diff --git a/config/config.go b/config/config.go index f82d64908..e27dd11eb 100644 --- a/config/config.go +++ b/config/config.go @@ -10,7 +10,10 @@ var Host string var JwtKey string var RelayUrl string var RelayAuthKey string + +// these are constants for the store var InvoiceList = "INVOICELIST" +var BudgetInvoiceList = "BUDGETINVOICELIST" func InitConfig() { Host = os.Getenv("LN_SERVER_BASE_URL") diff --git a/db/config.go b/db/config.go index 1ac93fea3..e63a566a9 100644 --- a/db/config.go +++ b/db/config.go @@ -191,7 +191,6 @@ func GetUserRolesMap(userRoles []UserRoles) map[string]string { } func RolesCheck(userRoles []UserRoles, check string) bool { - hasRole := false rolesMap := GetRolesMap() userRolesMap := GetUserRolesMap(userRoles) @@ -202,25 +201,29 @@ func RolesCheck(userRoles []UserRoles, check string) bool { // if any of the roles does not exists return false // if any of the roles does not exists user roles return false if !ok { - hasRole = false - return hasRole + return false } else if !ok1 { - hasRole = false - return hasRole + return false } - - hasRole = true - return hasRole + return true } func CheckUser(userRoles []UserRoles, pubkey string) bool { - isUser := false for _, role := range userRoles { if role.OwnerPubKey == pubkey { - isUser = true - return isUser + return true } } + return false +} - return isUser +func UserHasAccess(pubKeyFromAuth string, uuid string, role string) bool { + org := DB.GetOrganizationByUuid(uuid) + var hasRole bool = false + if pubKeyFromAuth != org.OwnerPubKey { + userRoles := DB.GetUserRoles(uuid, pubKeyFromAuth) + hasRole = RolesCheck(userRoles, role) + return hasRole + } + return true } diff --git a/db/db.go b/db/db.go index 71fee85a0..47aa4bfb3 100644 --- a/db/db.go +++ b/db/db.go @@ -418,7 +418,7 @@ func (db database) GetListedPosts(r *http.Request) ([]PeopleExtra, error) { return ms, result.Error } -func (db database) GetBountiesCounty(personKey string, tabType string) int64 { +func (db database) GetBountiesCount(personKey string, tabType string) int64 { var count int64 query := db.db.Model(&Bounty{}) @@ -432,7 +432,7 @@ func (db database) GetBountiesCounty(personKey string, tabType string) int64 { return count } -func (db database) GetOrganizationBounties(r *http.Request, org string) []BountyData { +func (db database) GetOrganizationBounties(r *http.Request, org_uuid string) []BountyData { keys := r.URL.Query() tags := keys.Get("tags") // this is a string of tags separated by commas offset, limit, sortBy, direction, search := utils.GetPaginationParams(r) @@ -453,7 +453,7 @@ func (db database) GetOrganizationBounties(r *http.Request, org string) []Bounty searchQuery = fmt.Sprintf("WHERE LOWER(body.title) LIKE %s", "'%"+search+"%'") } - rawQuery := "SELECT body.*, body.id as bounty_id, body.description as bounty_description, body.created as bounty_created, body.updated as bounty_updated, body.organization as organization, person.*, person.owner_alias as assignee_alias, person.id as assignee_id, person.description as assignee_description, person.created as assignee_created, person.updated as assignee_updated, owner.id as bounty_owner_id, owner.uuid as owner_uuid, owner.owner_pub_key as owner_key, owner.owner_alias as owner_alias, owner.description as owner_description, owner.price_to_meet as owner_price_to_meet, owner.unique_name as owner_unique_name, owner.tags as owner_tags, owner.img as owner_img, owner.created as owner_created, owner.updated as owner_updated, owner.last_login as owner_last_login, owner.owner_route_hint as owner_route_hint, owner.owner_contact_key as owner_contact_key, org.name as organization_name, org.uuid as organization_uuid, org.img as organization_img FROM public.bounty AS body LEFT OUTER JOIN public.people AS person ON body.assignee = person.owner_pub_key LEFT OUTER JOIN public.people as owner ON body.owner_id = owner.owner_pub_key LEFT OUTER JOIN public.organizations as org ON body.organization = org.uuid WHERE body.organization =" + `'` + org + `'` + rawQuery := "SELECT body.*, body.id as bounty_id, body.description as bounty_description, body.created as bounty_created, body.updated as bounty_updated, body.org_uuid, person.*, person.owner_alias as assignee_alias, person.id as assignee_id, person.description as assignee_description, person.created as assignee_created, person.updated as assignee_updated, owner.id as bounty_owner_id, owner.uuid as owner_uuid, owner.owner_pub_key as owner_key, owner.owner_alias as owner_alias, owner.description as owner_description, owner.price_to_meet as owner_price_to_meet, owner.unique_name as owner_unique_name, owner.tags as owner_tags, owner.img as owner_img, owner.created as owner_created, owner.updated as owner_updated, owner.last_login as owner_last_login, owner.owner_route_hint as owner_route_hint, owner.owner_contact_key as owner_contact_key, org.name as organization_name, org.uuid as organization_uuid, org.img as organization_img FROM public.bounty AS body LEFT OUTER JOIN public.people AS person ON body.assignee = person.owner_pub_key LEFT OUTER JOIN public.people as owner ON body.owner_id = owner.owner_pub_key LEFT OUTER JOIN public.organizations as org ON body.org_uuid = org.uuid WHERE body.org_uuid =" + `'` + org_uuid + `'` theQuery := db.db.Raw(rawQuery + " " + searchQuery + " " + orderQuery + " " + limitQuery) @@ -473,7 +473,7 @@ func (db database) GetOrganizationBounties(r *http.Request, org string) []Bounty func (db database) GetAssignedBounties(pubkey string) ([]BountyData, error) { ms := []BountyData{} - err := db.db.Raw(`SELECT body.*, body.id as bounty_id, body.description as bounty_description, body.created as bounty_created, body.updated as bounty_updated, body.organization as organization, person.*, person.owner_alias as assignee_alias, person.id as assignee_id, person.description as assignee_description, person.created as assignee_created, person.updated as assignee_updated, owner.id as bounty_owner_id, owner.uuid as owner_uuid, owner.owner_pub_key as owner_key, owner.owner_alias as owner_alias, owner.description as owner_description, owner.price_to_meet as owner_price_to_meet, owner.unique_name as owner_unique_name, owner.tags as owner_tags, owner.img as owner_img, owner.created as owner_created, owner.updated as owner_updated, owner.last_login as owner_last_login, owner.owner_route_hint as owner_route_hint, owner.owner_contact_key as owner_contact_key, org.name as organization_name, org.uuid as organization_uuid, org.img as organization_img FROM public.bounty AS body LEFT OUTER JOIN public.people AS person ON body.assignee = person.owner_pub_key LEFT OUTER JOIN public.people as owner ON body.owner_id = owner.owner_pub_key LEFT OUTER JOIN public.organizations as org ON body.organization = org.uuid WHERE body.assignee = '` + pubkey + `' ORDER BY body.id DESC`).Find(&ms).Error + err := db.db.Raw(`SELECT body.*, body.id as bounty_id, body.description as bounty_description, body.created as bounty_created, body.updated as bounty_updated, body.org_uuid, person.*, person.owner_alias as assignee_alias, person.id as assignee_id, person.description as assignee_description, person.created as assignee_created, person.updated as assignee_updated, owner.id as bounty_owner_id, owner.uuid as owner_uuid, owner.owner_pub_key as owner_key, owner.owner_alias as owner_alias, owner.description as owner_description, owner.price_to_meet as owner_price_to_meet, owner.unique_name as owner_unique_name, owner.tags as owner_tags, owner.img as owner_img, owner.created as owner_created, owner.updated as owner_updated, owner.last_login as owner_last_login, owner.owner_route_hint as owner_route_hint, owner.owner_contact_key as owner_contact_key, org.name as organization_name, org.uuid as organization_uuid, org.img as organization_img FROM public.bounty AS body LEFT OUTER JOIN public.people AS person ON body.assignee = person.owner_pub_key LEFT OUTER JOIN public.people as owner ON body.owner_id = owner.owner_pub_key LEFT OUTER JOIN public.organizations as org ON body.org_uuid = org.uuid WHERE body.assignee = '` + pubkey + `' ORDER BY body.id DESC`).Find(&ms).Error return ms, err } @@ -481,7 +481,7 @@ func (db database) GetAssignedBounties(pubkey string) ([]BountyData, error) { func (db database) GetCreatedBounties(pubkey string) ([]BountyData, error) { ms := []BountyData{} - err := db.db.Raw(`SELECT body.*, body.id as bounty_id, body.description as bounty_description, body.created as bounty_created, body.updated as bounty_updated, body.organization as organization, person.*, person.owner_alias as assignee_alias, person.id as assignee_id, person.description as assignee_description, person.created as assignee_created, person.updated as assignee_updated, owner.id as bounty_owner_id, owner.uuid as owner_uuid, owner.owner_pub_key as owner_key, owner.owner_alias as owner_alias, owner.description as owner_description, owner.price_to_meet as owner_price_to_meet, owner.unique_name as owner_unique_name, owner.tags as owner_tags, owner.img as owner_img, owner.created as owner_created, owner.updated as owner_updated, owner.last_login as owner_last_login, owner.owner_route_hint as owner_route_hint, owner.owner_contact_key as owner_contact_key, org.name as organization_name, org.uuid as organization_uuid, org.img as organization_img FROM public.bounty AS body LEFT OUTER JOIN public.people AS person ON body.assignee = person.owner_pub_key LEFT OUTER JOIN public.people as owner ON body.owner_id = owner.owner_pub_key LEFT OUTER JOIN public.organizations as org ON body.organization = org.uuid WHERE body.owner_id = '` + pubkey + `' ORDER BY body.id DESC`).Find(&ms).Error + err := db.db.Raw(`SELECT body.*, body.id as bounty_id, body.description as bounty_description, body.created as bounty_created, body.updated as bounty_updated, body.org_uuid, person.*, person.owner_alias as assignee_alias, person.id as assignee_id, person.description as assignee_description, person.created as assignee_created, person.updated as assignee_updated, owner.id as bounty_owner_id, owner.uuid as owner_uuid, owner.owner_pub_key as owner_key, owner.owner_alias as owner_alias, owner.description as owner_description, owner.price_to_meet as owner_price_to_meet, owner.unique_name as owner_unique_name, owner.tags as owner_tags, owner.img as owner_img, owner.created as owner_created, owner.updated as owner_updated, owner.last_login as owner_last_login, owner.owner_route_hint as owner_route_hint, owner.owner_contact_key as owner_contact_key, org.name as organization_name, org.uuid as organization_uuid, org.img as organization_img FROM public.bounty AS body LEFT OUTER JOIN public.people AS person ON body.assignee = person.owner_pub_key LEFT OUTER JOIN public.people as owner ON body.owner_id = owner.owner_pub_key LEFT OUTER JOIN public.organizations as org ON body.org_uuid = org.uuid WHERE body.owner_id = '` + pubkey + `' ORDER BY body.id DESC`).Find(&ms).Error return ms, err } @@ -489,7 +489,7 @@ func (db database) GetCreatedBounties(pubkey string) ([]BountyData, error) { func (db database) GetBountyById(id string) ([]BountyData, error) { ms := []BountyData{} - err := db.db.Raw(`SELECT body.*, body.id as bounty_id, body.description as bounty_description, body.created as bounty_created, body.updated as bounty_updated, body.organization as organization, person.*, person.owner_alias as assignee_alias, person.id as assignee_id, person.description as assignee_description, person.created as assignee_created, person.updated as assignee_updated, owner.id as bounty_owner_id, owner.uuid as owner_uuid, owner.owner_pub_key as owner_key, owner.owner_alias as owner_alias, owner.description as owner_description, owner.price_to_meet as owner_price_to_meet, owner.unique_name as owner_unique_name, owner.tags as owner_tags, owner.img as owner_img, owner.created as owner_created, owner.updated as owner_updated, owner.last_login as owner_last_login, owner.owner_route_hint as owner_route_hint, owner.owner_contact_key as owner_contact_key, org.name as organization_name, org.uuid as organization_uuid, org.img as organization_img FROM public.bounty AS body LEFT OUTER JOIN public.people AS person ON body.assignee = person.owner_pub_key LEFT OUTER JOIN public.people as owner ON body.owner_id = owner.owner_pub_key LEFT OUTER JOIN public.organizations as org ON body.organization = org.uuid WHERE body.id = '` + id + `' ORDER BY body.id DESC`).Find(&ms).Error + err := db.db.Raw(`SELECT body.*, body.id as bounty_id, body.description as bounty_description, body.created as bounty_created, body.updated as bounty_updated, body.org_uuid, person.*, person.owner_alias as assignee_alias, person.id as assignee_id, person.description as assignee_description, person.created as assignee_created, person.updated as assignee_updated, owner.id as bounty_owner_id, owner.uuid as owner_uuid, owner.owner_pub_key as owner_key, owner.owner_alias as owner_alias, owner.description as owner_description, owner.price_to_meet as owner_price_to_meet, owner.unique_name as owner_unique_name, owner.tags as owner_tags, owner.img as owner_img, owner.created as owner_created, owner.updated as owner_updated, owner.last_login as owner_last_login, owner.owner_route_hint as owner_route_hint, owner.owner_contact_key as owner_contact_key, org.name as organization_name, org.uuid as organization_uuid, org.img as organization_img FROM public.bounty AS body LEFT OUTER JOIN public.people AS person ON body.assignee = person.owner_pub_key LEFT OUTER JOIN public.people as owner ON body.owner_id = owner.owner_pub_key LEFT OUTER JOIN public.organizations as org ON body.org_uuid = org.uuid WHERE body.id = '` + id + `' ORDER BY body.id DESC`).Find(&ms).Error return ms, err } @@ -519,7 +519,7 @@ func (db database) GetAllBounties(r *http.Request) []BountyData { searchQuery = fmt.Sprintf("WHERE LOWER(body.title) LIKE %s", "'%"+search+"%'") } - rawQuery := "SELECT body.*, body.id as bounty_id, body.description as bounty_description, body.created as bounty_created, body.updated as bounty_updated, body.organization as organization, person.*, person.owner_alias as assignee_alias, person.id as assignee_id, person.description as assignee_description, person.created as assignee_created, person.updated as assignee_updated, owner.id as bounty_owner_id, owner.uuid as owner_uuid, owner.owner_pub_key as owner_key, owner.owner_alias as owner_alias, owner.description as owner_description, owner.price_to_meet as owner_price_to_meet, owner.unique_name as owner_unique_name, owner.tags as owner_tags, owner.img as owner_img, owner.created as owner_created, owner.updated as owner_updated, owner.last_login as owner_last_login, owner.owner_route_hint as owner_route_hint, owner.owner_contact_key as owner_contact_key, org.name as organization_name, org.uuid as organization_uuid, org.img as organization_img FROM public.bounty AS body LEFT OUTER JOIN public.people AS person ON body.assignee = person.owner_pub_key LEFT OUTER JOIN public.people as owner ON body.owner_id = owner.owner_pub_key LEFT OUTER JOIN public.organizations as org ON body.organization = org.uuid" + rawQuery := "SELECT body.*, body.id as bounty_id, body.description as bounty_description, body.created as bounty_created, body.updated as bounty_updated, body.org_uuid, person.*, person.owner_alias as assignee_alias, person.id as assignee_id, person.description as assignee_description, person.created as assignee_created, person.updated as assignee_updated, owner.id as bounty_owner_id, owner.uuid as owner_uuid, owner.owner_pub_key as owner_key, owner.owner_alias as owner_alias, owner.description as owner_description, owner.price_to_meet as owner_price_to_meet, owner.unique_name as owner_unique_name, owner.tags as owner_tags, owner.img as owner_img, owner.created as owner_created, owner.updated as owner_updated, owner.last_login as owner_last_login, owner.owner_route_hint as owner_route_hint, owner.owner_contact_key as owner_contact_key, org.name as organization_name, org.uuid as organization_uuid, org.img as organization_img FROM public.bounty AS body LEFT OUTER JOIN public.people AS person ON body.assignee = person.owner_pub_key LEFT OUTER JOIN public.people as owner ON body.owner_id = owner.owner_pub_key LEFT OUTER JOIN public.organizations as org ON body.org_uuid = org.uuid" theQuery := db.db.Raw(rawQuery + " " + searchQuery + " " + orderQuery + " " + limitQuery) @@ -559,6 +559,12 @@ func (db database) GetBountyByCreated(created uint) (Bounty, error) { return b, err } +func (db database) GetBounty(id uint) Bounty { + b := Bounty{} + db.db.Where("id", id).Find(&b) + return b +} + func (db database) UpdateBounty(b Bounty) (Bounty, error) { db.db.Where("created", b.Created).Updates(&b) return b, nil @@ -1008,21 +1014,21 @@ func (db database) CreateOrEditOrganization(m Organization) (Organization, error func (db database) GetOrganizationUsers(uuid string) ([]OrganizationUsersData, error) { ms := []OrganizationUsersData{} - err := db.db.Raw(`SELECT org.organization, org.created as user_created, person.* FROM public.organization_users AS org LEFT OUTER JOIN public.people AS person ON org.owner_pub_key = person.owner_pub_key WHERE org.organization = '` + uuid + `' ORDER BY org.created DESC`).Find(&ms).Error + err := db.db.Raw(`SELECT org.org_uuid, org.created as user_created, person.* FROM public.organization_users AS org LEFT OUTER JOIN public.people AS person ON org.owner_pub_key = person.owner_pub_key WHERE org.org_uuid = '` + uuid + `' ORDER BY org.created DESC`).Find(&ms).Error return ms, err } func (db database) GetOrganizationUsersCount(uuid string) int64 { var count int64 - db.db.Model(&OrganizationUsers{}).Where("organization = ?", uuid).Count(&count) + db.db.Model(&OrganizationUsers{}).Where("org_uuid = ?", uuid).Count(&count) return count } -func (db database) GetOrganizationUser(pubkey string, org string) OrganizationUsers { +func (db database) GetOrganizationUser(pubkey string, org_uuid string) OrganizationUsers { ms := OrganizationUsers{} - db.db.Where("organization = ?", org).Where("owner_pub_key = ?", pubkey).Find(&ms) + db.db.Where("org_uuid = ?", org_uuid).Where("owner_pub_key = ?", pubkey).Find(&ms) return ms } @@ -1047,7 +1053,7 @@ func (db database) GetBountyRoles() []BountyRoles { func (db database) CreateUserRoles(roles []UserRoles, uuid string, pubkey string) []UserRoles { // delete roles and create new ones - db.db.Where("organization = ?", uuid).Where("owner_pub_key = ?", pubkey).Delete(&UserRoles{}) + db.db.Where("org_uuid = ?", uuid).Where("owner_pub_key = ?", pubkey).Delete(&UserRoles{}) db.db.Create(&roles) return roles @@ -1055,7 +1061,7 @@ func (db database) CreateUserRoles(roles []UserRoles, uuid string, pubkey string func (db database) GetUserRoles(uuid string, pubkey string) []UserRoles { ms := []UserRoles{} - db.db.Where("organization = ?", uuid).Where("owner_pub_key = ?", pubkey).Find(&ms) + db.db.Where("org_uuid = ?", uuid).Where("owner_pub_key = ?", pubkey).Find(&ms) return ms } @@ -1070,3 +1076,80 @@ func (db database) GetUserAssignedOrganizations(pubkey string) []OrganizationUse db.db.Where("owner_pub_key = ?", pubkey).Find(&ms) return ms } + +func (db database) AddBudgetHistory(budget BudgetHistory) BudgetHistory { + db.db.Create(&budget) + return budget +} + +func (db database) CreateOrganizationBudget(budget BountyBudget) BountyBudget { + db.db.Create(&budget) + return budget +} + +func (db database) UpdateOrganizationBudget(budget BountyBudget) BountyBudget { + db.db.Where("org_uuid = ?", budget.OrgUuid).Updates(budget) + return budget +} + +func (db database) GetBudgetHistoryByCreated(created *time.Time, org_uuid string) BudgetHistory { + ms := BudgetHistory{} + db.db.Where("created = ?", created).Where("org_uuid = ? ", org_uuid).Find(&ms) + return ms +} + +func (db database) GetOrganizationBudget(org_uuid string) BountyBudget { + ms := BountyBudget{} + db.db.Where("org_uuid = ?", org_uuid).Find(&ms) + return ms +} + +func (db database) AddAndUpdateBudget(budget BudgetStoreData) BudgetHistory { + created := budget.Created + org_uuid := budget.OrgUuid + + budgetHistory := db.GetBudgetHistoryByCreated(created, org_uuid) + + if budgetHistory.OrgUuid != "" && budgetHistory.Amount != 0 { + budgetHistory.Status = true + db.db.Where("created = ?", created).Where("org_uuid = ? ", org_uuid).Updates(budgetHistory) + + // get organization budget and add payment to total budget + organizationBudget := db.GetOrganizationBudget(org_uuid) + + if organizationBudget.OrgUuid == "" { + now := time.Now() + orgBudget := BountyBudget{ + OrgUuid: org_uuid, + TotalBudget: budget.Amount, + Created: &now, + Updated: &now, + } + db.CreateOrganizationBudget(orgBudget) + } else { + totalBudget := organizationBudget.TotalBudget + organizationBudget.TotalBudget = totalBudget + budget.Amount + db.UpdateOrganizationBudget(organizationBudget) + } + } + + return budgetHistory +} + +func (db database) AddPaymentHistory(payment PaymentHistory) PaymentHistory { + db.db.Create(&payment) + + // get organization budget and substract payment from total budget + organizationBudget := db.GetOrganizationBudget(payment.OrgUuid) + totalBudget := organizationBudget.TotalBudget + organizationBudget.TotalBudget = totalBudget - payment.Amount + db.UpdateOrganizationBudget(organizationBudget) + + return payment +} + +func (db database) GetPaymentHistory(org_uuid string) []PaymentHistoryData { + payment := []PaymentHistoryData{} + db.db.Raw(`SELECT payment.id, payment.org_uuid, payment.amount, payment.bounty_id as bounty_id, payment.created, sender.unique_name AS sender_name, receiver.unique_name as receiver_name FROM public.payment_histories AS payment LEFT OUTER JOIN public.people AS sender ON payment.sender_pub_key = sender.owner_pub_key LEFT OUTER JOIN public.people AS receiver ON payment.receiver_pub_key = receiver.owner_pub_key WHERE payment.org_uuid = '` + org_uuid + `' ORDER BY payment.created DESC`).Find(&payment) + return payment +} diff --git a/db/store.go b/db/store.go index 4cac7352e..edd64ffe3 100644 --- a/db/store.go +++ b/db/store.go @@ -86,6 +86,21 @@ func (s StoreData) GetInvoiceCache() ([]InvoiceStoreData, error) { return c, nil } +func (s StoreData) SetBudgetInvoiceCache(value []BudgetStoreData) error { + // The invoice should expire every 6 minutes + s.Cache.Set(config.BudgetInvoiceList, value, 6*time.Minute) + return nil +} + +func (s StoreData) GetBudgetInvoiceCache() ([]BudgetStoreData, error) { + value, found := s.Cache.Get(config.BudgetInvoiceList) + c, _ := value.([]BudgetStoreData) + if !found { + return []BudgetStoreData{}, errors.New("Budget Invoice Cache not found") + } + return c, nil +} + func (s StoreData) SetSocketConnections(value Client) error { // The websocket in cache should not expire unless when deleted s.Cache.Set(value.Host, value, cache.NoExpiration) diff --git a/db/structs.go b/db/structs.go index b28a65de6..58d8a7be3 100644 --- a/db/structs.go +++ b/db/structs.go @@ -343,7 +343,7 @@ type Bounty struct { Created int64 `json:"created"` Assignee string `json:"assignee"` TicketUrl string `json:"ticket_url"` - Organization string `json:"organization"` + OrgUuid string `json:"org_uuid"` Description string `json:"description"` WantedType string `json:"wanted_type"` Deliverables string `json:"deliverables"` @@ -412,16 +412,16 @@ type OrganizationShort struct { } type OrganizationUsers struct { - ID uint `json:"id"` - OwnerPubKey string `json:"owner_pubkey"` - Organization string `json:"organization"` - Created *time.Time `json:"created"` - Updated *time.Time `json:"updated"` + ID uint `json:"id"` + OwnerPubKey string `json:"owner_pubkey"` + OrgUuid string `json:"org_uuid"` + Created *time.Time `json:"created"` + Updated *time.Time `json:"updated"` } type OrganizationUsersData struct { - Organization string `json:"organization"` - UserCreated *time.Time `json:"user_created"` + OrgUuid string `json:"org_uuid"` + UserCreated *time.Time `json:"user_created"` Person } @@ -430,36 +430,71 @@ type BountyRoles struct { } type UserRoles struct { - Role string `json:"role"` - OwnerPubKey string `json:"owner_pubkey"` - Organization string `json:"organization"` - Created *time.Time `json:"created"` + Role string `json:"role"` + OwnerPubKey string `json:"owner_pubkey"` + OrgUuid string `json:"org_uuid"` + Created *time.Time `json:"created"` } type BountyBudget struct { - Organization string `json:"organization"` - TotalBudget uint `json:"total_budget"` + ID uint `json:"id"` + OrgUuid string `json:"org_uuid"` + TotalBudget uint `json:"total_budget"` + Created *time.Time `json:"created"` + Updated *time.Time `json:"updated"` +} + +type BudgetInvoiceRequest struct { + Amount uint `json:"amount"` + SenderPubKey string `json:"sender_pubkey"` + OrgUuid string `json:"org_uuid"` + Websocket_token string `json:"websocket_token,omitempty"` +} + +type BudgetStoreData struct { + Amount uint `json:"amount"` + SenderPubKey string `json:"sender_pubkey"` + OrgUuid string `json:"org_uuid"` + Invoice string `json:"invoice"` + Host string `json:"host,omitempty"` Created *time.Time `json:"created"` - Updated *time.Time `json:"updated"` } type BudgetHistory struct { - Organization string `json:"organization"` + ID uint `json:"id"` + OrgUuid string `json:"org_uuid"` Amount uint `json:"amount"` SenderPubKey string `json:"sender_pubkey"` Created *time.Time `json:"created"` Updated *time.Time `json:"updated"` + Status bool `json:"status"` } type PaymentHistory struct { - Organization string `json:"organization"` + ID uint `json:"id"` + OrgUuid string `json:"org_uuid"` SenderPubKey string `json:"sender_pubkey"` ReceiverPubKey string `json:"receiver_pubkey"` Amount uint `json:"amount"` - BountyId uint `json:"id"` + BountyId uint `json:"bounty_id"` Created *time.Time `json:"created"` } +type PaymentHistoryData struct { + ID uint `json:"id"` + OrgUuid string `json:"org_uuid"` + SenderName string `json:"sender_name"` + ReceiverName string `json:"receiver_name"` + Amount uint `json:"amount"` + BountyId uint `json:"bounty_id"` + Created *time.Time `json:"created"` +} + +type BountyPayRequest struct { + ReceiverPubKey string `json:"receiver_pubkey"` + Websocket_token string `json:"websocket_token,omitempty"` +} + func (Person) TableName() string { return "people" } diff --git a/frontend/app/src/App.css b/frontend/app/src/App.css index 9d180f43d..a6bc4a743 100644 --- a/frontend/app/src/App.css +++ b/frontend/app/src/App.css @@ -125,13 +125,11 @@ button:focus { } #header .euiFieldSearch:focus { - background-image: linear-gradient( - to top, - #5d8fdd, - #5d8fdd 2px, - transparent 2px, - transparent 100% - ); + background-image: linear-gradient(to top, + #5d8fdd, + #5d8fdd 2px, + transparent 2px, + transparent 100%); } #header-right { @@ -514,13 +512,11 @@ textarea.qr-string { } #header .euiFieldSearch:focus { - background-image: linear-gradient( - to top, - #5d8fdd, - #5d8fdd 2px, - transparent 2px, - transparent 100% - ); + background-image: linear-gradient(to top, + #5d8fdd, + #5d8fdd 2px, + transparent 2px, + transparent 100%); } #header-right { @@ -875,7 +871,7 @@ textarea.qr-string { border: none; } -.react-datepicker__year-dropdown-container > div:not([class*='read-view']), +.react-datepicker__year-dropdown-container>div:not([class*='read-view']), .react-datepicker__month-dropdown { background: #fff !important; color: #000 !important; @@ -974,10 +970,7 @@ textarea.qr-string { background-color: #ffffff00 !important; } -.euiFormRow__fieldWrapper - .euiFormControlLayout--group - .euiFormLabel - .euiFormControlLayout__prepend { +.euiFormRow__fieldWrapper .euiFormControlLayout--group .euiFormLabel .euiFormControlLayout__prepend { background-color: #ffffff00 !important; } @@ -1021,8 +1014,8 @@ input::-webkit-search-cancel-button { } .euiPanel:focus-visible, -.euiPanel > div:focus-visible, -.euiPanel > div > div:focus-visible { +.euiPanel>div:focus-visible, +.euiPanel>div>div:focus-visible { outline: none; border: none; } @@ -1084,3 +1077,18 @@ input::-webkit-search-cancel-button { text-decoration: none; text-transform: none; } + +.ellipsis { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100px; +} + +table { + width: 100%; +} + +td { + padding: 10px 0px; +} \ No newline at end of file diff --git a/frontend/app/src/__test__/__mockData__/user.ts b/frontend/app/src/__test__/__mockData__/user.ts index 717d6a380..7112eea5c 100644 --- a/frontend/app/src/__test__/__mockData__/user.ts +++ b/frontend/app/src/__test__/__mockData__/user.ts @@ -9,6 +9,7 @@ export const user: MeInfo = { route_hint: 'test_hint:1099567661057', price_to_meet: 0, jwt: 'test_jwt', + tribe_jwt: 'test_jwt', url: 'https://mockApi.com', description: 'description', verification_signature: 'test_verification_signature', diff --git a/frontend/app/src/components/form/schema.ts b/frontend/app/src/components/form/schema.ts index dc9ed2f26..0436ca6f2 100644 --- a/frontend/app/src/components/form/schema.ts +++ b/frontend/app/src/components/form/schema.ts @@ -690,8 +690,8 @@ export const wantedOtherSchema: FormField[] = [ export const wantedCodingTaskSchema: FormField[] = [ { - name: 'organization', - label: 'Organization', + name: 'org_uuid', + label: 'Organization(optional)', type: 'select', options: [], validator: strValidatorNotRequired diff --git a/frontend/app/src/config/socket.ts b/frontend/app/src/config/socket.ts index a2f6d60ec..ec892e8d0 100644 --- a/frontend/app/src/config/socket.ts +++ b/frontend/app/src/config/socket.ts @@ -11,7 +11,8 @@ export const SOCKET_MSG = { invoice_success: 'invoice_success', assign_success: 'assign_success', lnauth_success: 'lnauth_success', - user_connect: 'user_connect' + user_connect: 'user_connect', + budget_success: 'budget_success' }; let socket: WebSocket | null = null; diff --git a/frontend/app/src/people/auth/SignIn.tsx b/frontend/app/src/people/auth/SignIn.tsx index 3ab9415e8..7eea7313a 100644 --- a/frontend/app/src/people/auth/SignIn.tsx +++ b/frontend/app/src/people/auth/SignIn.tsx @@ -102,7 +102,7 @@ function SignIn(props: AuthProps) { main.setLnAuth({ encode: '', k1: '' }); main.setLnToken(res.jwt); - ui.setMeInfo({ ...res.user, jwt: res.jwt }); + ui.setMeInfo({ ...res.user, jwt: res.jwt, tribe_jwt: res.jwt }); ui.setSelectedPerson(res.id); } } diff --git a/frontend/app/src/people/interfaces.ts b/frontend/app/src/people/interfaces.ts index d8e38b5e9..0305a3a4c 100644 --- a/frontend/app/src/people/interfaces.ts +++ b/frontend/app/src/people/interfaces.ts @@ -59,7 +59,7 @@ export interface BountiesProps { created?: number; ticketUrl?: string; loomEmbedUrl?: string; - organization?: string; + org_uuid?: string; description?: any; isPaid: boolean; widget?: any; @@ -152,11 +152,12 @@ export interface PaidBountiesProps { description: string; owner_alias: string; owner_pubkey: string; - organization?: string; + org_uuid?: string; img: string; id: number; widget: any; created: number; + name?: string; } export interface QRProps { @@ -233,7 +234,8 @@ export interface WantedSummaryProps { setIsExtraStyle: (any) => void; formSubmit: (any) => void; title: string; - organization?: string; + org_uuid?: string; + id?: number; } export interface CodingBountiesProps { @@ -294,6 +296,8 @@ export interface CodingBountiesProps { extraModalFunction?: () => void; commitment_fee?: number; bounty_expires?: string; + org_uuid?: string; + id?: number; } export interface CodingViewProps { @@ -350,7 +354,7 @@ export interface WantedViewsProps { commitment_fee?: number; name?: string; img?: string; - uuid?: string; + org_uuid?: string; } export interface WantedViews2Props extends WantedViewsProps { diff --git a/frontend/app/src/people/main/FocusView.tsx b/frontend/app/src/people/main/FocusView.tsx index 2892dca74..d231bf424 100644 --- a/frontend/app/src/people/main/FocusView.tsx +++ b/frontend/app/src/people/main/FocusView.tsx @@ -64,9 +64,9 @@ const B = styled.div` overflow-y: auto; box-sizing: border-box; ${EnvWithScrollBar({ - thumbColor: '#5a606c', - trackBackgroundColor: 'rgba(0,0,0,0)' - })} + thumbColor: '#5a606c', + trackBackgroundColor: 'rgba(0,0,0,0)' +})} `; function FocusedView(props: FocusViewProps) { const { @@ -371,8 +371,8 @@ function FocusedView(props: FocusViewProps) { extraHTML={ ui.meInfo.verification_signature ? { - twitter: `Post this to your twitter account to verify:
Sphinx Verification: ${ui.meInfo.verification_signature}` - } + twitter: `Post this to your twitter account to verify:
Sphinx Verification: ${ui.meInfo.verification_signature}` + } : {} } /> diff --git a/frontend/app/src/people/utils/AssignBounty.tsx b/frontend/app/src/people/utils/AssignBounty.tsx index f05eee5eb..9bd874454 100644 --- a/frontend/app/src/people/utils/AssignBounty.tsx +++ b/frontend/app/src/people/utils/AssignBounty.tsx @@ -1,5 +1,4 @@ import React, { useEffect, useState } from 'react'; -import styled from 'styled-components'; import { ConnectCardProps } from 'people/interfaces'; import { useStores } from 'store'; import { EuiGlobalToastList } from '@elastic/eui'; @@ -8,51 +7,8 @@ import { SOCKET_MSG, createSocketInstance } from 'config/socket'; import Invoice from '../widgetViews/summaries/wantedSummaries/Invoice'; import { colors } from '../../config/colors'; import { Button, Modal } from '../../components/common'; +import { InvoiceInput, InvoiceLabel, InvoiceForm, B, N, ModalBottomText } from './style' -interface styledProps { - color?: any; -} - -const B = styled.small` - font-weight: bold; - display: block; - margin-bottom: 10px; -`; -const N = styled.div` - font-family: Barlow; - font-style: normal; - font-weight: 500; - font-size: 17px; - line-height: 26px; - text-align: center; - margin-bottom: 10px; - color: ${(p: any) => p?.color && p?.color.grayish.G100}; -`; -const ModalBottomText = styled.div` - position: absolute; - bottom: -36px; - width: 310; - background-color: transparent; - display: flex; - justify-content: center; - .bottomText { - margin-left: 12px; - color: ${(p: any) => p?.color && p?.color.pureWhite}; - } -`; -const InvoiceForm = styled.div` - margin: 10px 0px; - text-align: left; -`; -const InvoiceLabel = styled.label` - font-size: 0.9rem; - font-weight: bold; -`; -const InvoiceInput = styled.input` - padding: 10px 20px; - border-radius: 10px; - border: 0.5px solid black; -`; export default function AssignBounty(props: ConnectCardProps) { const color = colors['light']; const { person, created, visible } = props; diff --git a/frontend/app/src/people/utils/AssignedUnassignedBounties.tsx b/frontend/app/src/people/utils/AssignedUnassignedBounties.tsx index 3f0af51c6..c70af2cfc 100644 --- a/frontend/app/src/people/utils/AssignedUnassignedBounties.tsx +++ b/frontend/app/src/people/utils/AssignedUnassignedBounties.tsx @@ -12,6 +12,7 @@ import BountyProfileView from '../../bounties/BountyProfileView'; import IconButton from '../../components/common/IconButton2'; import ConnectCard from './ConnectCard'; import StartUpModal from './StartUpModal'; +import { OrganizationWrap, OrganizationText } from './style'; interface containerProps { unAssignedBackgroundImage?: string; @@ -108,28 +109,6 @@ const UnassignedPersonProfile = styled.div` } `; -const OrganizationWrap = styled.div` - margin-left: 0px; - cursor: pointer; - padding: 0px; - background: white; - padding: 2px 10px; - max-width: 180px; - text-align: center; - border-radius: 0px; - display: block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -`; - -const OrganizationText = styled.span` - font-weight: bold; - font-size: 0.9rem; - text-transform: capitalize; - color: #20c997; -`; - const Bounties = (props: BountiesProps) => { const { assignee, @@ -143,7 +122,7 @@ const Bounties = (props: BountiesProps) => { onPanelClick, widget, created, - uuid, + org_uuid, name } = props; @@ -158,9 +137,9 @@ const Bounties = (props: BountiesProps) => { const { ui } = useStores(); return ( <> - {uuid && name && ( + {org_uuid && name && ( - + {name} diff --git a/frontend/app/src/people/utils/BountyCreationConstant.ts b/frontend/app/src/people/utils/BountyCreationConstant.ts index 8b49468c8..5812953de 100644 --- a/frontend/app/src/people/utils/BountyCreationConstant.ts +++ b/frontend/app/src/people/utils/BountyCreationConstant.ts @@ -20,7 +20,7 @@ export const BountyDetailsCreationData = { schemaName: 'Freelance Job Request', heading: 'Basic info', sub_heading: ' ', - schema: ['organization', 'one_sentence_summary', 'ticket_url'], + schema: ['org_uuid', 'one_sentence_summary', 'ticket_url'], schema2: ['wanted_type', 'coding_languages'], required: ['one_sentence_summary', 'wanted_type'], outerContainerStyle: { diff --git a/frontend/app/src/people/utils/PaidBounty.tsx b/frontend/app/src/people/utils/PaidBounty.tsx index c23f10dd7..affbbf520 100644 --- a/frontend/app/src/people/utils/PaidBounty.tsx +++ b/frontend/app/src/people/utils/PaidBounty.tsx @@ -1,10 +1,12 @@ import React from 'react'; import styled from 'styled-components'; import { PaidBountiesProps } from 'people/interfaces'; +import { Link } from 'react-router-dom'; import BountyDescription from '../../bounties/BountyDescription'; import BountyPrice from '../../bounties/BountyPrice'; import BountyProfileView from '../../bounties/BountyProfileView'; import { colors } from '../../config/colors'; +import { OrganizationWrap, OrganizationText } from './style'; interface PaidBountyProps { Price_User_Container_Border?: string; @@ -37,52 +39,66 @@ const PriceUserContainer = styled.div` `; const PaidBounty = (props: PaidBountiesProps) => { const color = colors['light']; + const { + org_uuid, + name + } = props; + return ( - - - - + {org_uuid && name && ( + + + {name} + + + )} + + - + + + + {'paid_ribbon'} - - {'paid_ribbon'} - + + ); }; diff --git a/frontend/app/src/people/utils/style.ts b/frontend/app/src/people/utils/style.ts new file mode 100644 index 000000000..2aba9a8ee --- /dev/null +++ b/frontend/app/src/people/utils/style.ts @@ -0,0 +1,66 @@ +import styled from 'styled-components'; + +interface styledProps { + color?: any; +} + +export const B = styled.small` + font-weight: bold; + display: block; + margin-bottom: 10px; +`; +export const N = styled.div` + font-family: Barlow; + font-style: normal; + font-weight: 500; + font-size: 17px; + line-height: 26px; + text-align: center; + margin-bottom: 10px; + color: ${(p: any) => p?.color && p?.color.grayish.G100}; +`; +export const ModalBottomText = styled.div` + position: absolute; + bottom: -36px; + width: 310; + background-color: transparent; + display: flex; + justify-content: center; + .bottomText { + margin-left: 12px; + color: ${(p: any) => p?.color && p?.color.pureWhite}; + } +`; +export const InvoiceForm = styled.div` + margin: 10px 0px; + text-align: left; +`; +export const InvoiceLabel = styled.label` + font-size: 0.9rem; + font-weight: bold; +`; +export const InvoiceInput = styled.input` + padding: 10px 20px; + border-radius: 10px; + border: 0.5px solid black; +`; +export const OrganizationWrap = styled.div` + margin-left: 0px; + cursor: pointer; + padding: 0px; + background: white; + padding: 2px 10px; + max-width: 180px; + text-align: center; + border-radius: 0px; + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`; +export const OrganizationText = styled.span` + font-weight: bold; + font-size: 0.9rem; + text-transform: capitalize; + color: #20c997; +`; \ No newline at end of file diff --git a/frontend/app/src/people/widgetViews/OrganizationDetails.tsx b/frontend/app/src/people/widgetViews/OrganizationDetails.tsx index 97f01bde7..64252e72b 100644 --- a/frontend/app/src/people/widgetViews/OrganizationDetails.tsx +++ b/frontend/app/src/people/widgetViews/OrganizationDetails.tsx @@ -3,16 +3,20 @@ import styled from 'styled-components'; import { useStores } from 'store'; import { Wrap } from 'components/form/style'; import { EuiGlobalToastList } from '@elastic/eui'; +import { InvoiceForm, InvoiceInput, InvoiceLabel } from 'people/utils/style'; +import moment from 'moment'; +import { SOCKET_MSG, createSocketInstance } from 'config/socket'; import { Button, IconButton } from 'components/common'; import { useIsMobile } from 'hooks/uiHooks'; import { Formik } from 'formik'; import { FormField, validator } from 'components/form/utils'; -import { BountyRoles, Organization, Person } from 'store/main'; +import { BountyRoles, Organization, PaymentHistory, Person } from 'store/main'; import MaterialIcon from '@material/react-material-icon'; import { userHasRole } from 'helpers'; import { Modal } from '../../components/common'; import { colors } from '../../config/colors'; import { nonWidgetConfigs } from '../utils/Constants'; +import Invoice from '../widgetViews/summaries/wantedSummaries/Invoice'; import Input from '../../components/form/inputs'; const color = colors['light']; @@ -32,9 +36,31 @@ const DetailsWrap = styled.div` padding: 0px 20px; `; -const UsersCount = styled.h3` - font-size: 1.3rem; - margin-bottom: 15px; +const OrgInfoWrap = styled.div` + display: flex; + align-items: center; +`; + +const DataCount = styled.div` + margin-bottom: 15px; + display: flex; + align-items: center; + margin-right: 20px; +`; + +const DataText = styled.h3` + font-size: 1.3rem; + padding: 0px; + margin: 0px; + margin-right: 10px; +`; + +const ViewHistoryText = styled.p` + padding: 0px; + margin: 0px; + margin-left: 10px; + font-size: 0.9rem; + cursor: pointer; `; const UsersTable = styled.div` @@ -47,453 +73,679 @@ const TableRow = styled.div` display: flex; flex-direction: row; padding: 10px; -`; +` const TableHead = styled.div` display: flex; flex-direction: row; padding: 10px; - background: #d3d3d3; + background: #D3D3D3; `; const ModalTitle = styled.h3` - font-size: 1.2rem; + font-size: 1.2rem; `; const Th = styled.div` - font-size: 1.1rem; - font-weight: bold; - min-width: 25%; -`; + font-size: 1.1rem; + font-weight: bold; + min-width: 25%; + `; const ThKey = styled.div` - font-size: 1.1rem; - font-weight: bold; - min-width: 50%; -`; + font-size: 1.1rem; + font-weight: bold; + min-width: 50%; + `; const Td = styled.div` - font-size: 0.95rem; - min-width: 25%; - text-transform: capitalize; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -`; + font-size: 0.95rem; + min-width: 25%; + text-transform: capitalize; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + `; const TdKey = styled.div` - font-size: 0.95rem; - min-width: 50%; - text-transform: capitalize; -`; + font-size: 0.95rem; + min-width: 50%; + text-transform: capitalize; + `; const Actions = styled.div` - font-size: 0.95rem; - min-width: 25%; -`; + font-size: 0.95rem; + min-width: 25%; + `; const CheckUl = styled.ul` - list-style: none; - padding: 0; - margin-top: 20px; + list-style: none; + padding: 0; + margin-top: 20px; `; const CheckLi = styled.li` - display: flex; - flex-direction: row; - align-items: center; - padding: 0px; - margin-bottom: 10px; + display: flex; + flex-direction: row; + align-items: center; + padding: 0px; + margin-bottom: 10px; `; const Check = styled.input` - width: 20px; - height: 20px; - border-radius: 5px; - padding: 0px; - margin-right: 10px; + width: 20px; + height: 20px; + border-radius: 5px; + padding: 0px; + margin-right: 10px; `; const CheckLabel = styled.label` - padding: 0px; - margin: 0px; + padding: 0px; + margin: 0px; `; -const OrganizationDetails = (props: { close: () => void; org: Organization | undefined }) => { - const [loading, setIsLoading] = useState(false); - const isMobile = useIsMobile(); - const { main, ui } = useStores(); - const [isOpen, setIsOpen] = useState(false); - const [isOpenRoles, setIsOpenRoles] = useState(false); - const [usersCount, setUsersCount] = useState(0); - const [disableFormButtons, setDisableFormButtons] = useState(false); - const [users, setUsers] = useState([]); - const [user, setUser] = useState(); - const [userRoles, setUserRoles] = useState([]); - const [bountyRoles, setBountyRoles] = useState([]); - const [bountyRolesData, setBountyRolesData] = useState([]); - const [toasts, setToasts]: any = useState([]); - - const config = nonWidgetConfigs['organizationusers']; - - const formRef = useRef(null); - const isOrganizationAdmin = props.org?.owner_pubkey === ui.meInfo?.owner_pubkey; - const schema = [...config.schema]; - - const initValues = { - owner_pubkey: '' - }; - - const uuid = props.org?.uuid; - - function addToast(title: string) { - setToasts([ - { - id: '1', - title, - color: 'danger' - } - ]); - } - - function removeToast() { - setToasts([]); - } - - const getOrganizationUsersCount = useCallback(async () => { - if (uuid) { - const count = await main.getOrganizationUsersCount(uuid); - setUsersCount(count); +const OrganizationDetails = (props: { close: () => void, org: Organization | undefined }) => { + const [loading, setIsLoading] = useState(false); + const isMobile = useIsMobile(); + const { main, ui } = useStores(); + const [isOpen, setIsOpen] = useState(false); + const [isOpenRoles, setIsOpenRoles] = useState(false); + const [isOpenBudget, setIsOpenBudget] = useState(false); + const [isOpenHistory, setIsOpenHistory] = useState(false); + const [usersCount, setUsersCount] = useState(0); + const [orgBudget, setOrgBudget] = useState(0); + const [paymentsHistory, setPaymentsHistory] = useState([]) + const [disableFormButtons, setDisableFormButtons] = useState(false); + const [users, setUsers] = useState([]); + const [user, setUser] = useState(); + const [userRoles, setUserRoles] = useState([]); + const [bountyRoles, setBountyRoles] = useState([]); + const [bountyRolesData, setBountyRolesData] = useState([]); + const [toasts, setToasts]: any = useState([]); + const [lnInvoice, setLnInvoice] = useState(''); + const [invoiceStatus, setInvoiceStatus] = useState(false); + const [amount, setAmount] = useState(1); + + const pollMinutes = 2; + + const config = nonWidgetConfigs['organizationusers']; + + const formRef = useRef(null); + const isOrganizationAdmin = props.org?.owner_pubkey === ui.meInfo?.owner_pubkey; + const schema = [...config.schema]; + + const initValues = { + owner_pubkey: '', + }; + + const uuid = props.org?.uuid || ''; + + function addToast(title: string, color: 'danger' | 'success') { + setToasts([ + { + id: '1', + title, + color + } + ]); } - }, [main, uuid]); - const getOrganizationUsers = useCallback(async () => { - if (uuid) { - const users = await main.getOrganizationUsers(uuid); - setUsers(users); + function removeToast() { + setToasts([]); } - }, [main, uuid]); - - const deleteOrganizationUser = async (user: any) => { - if (uuid) { - const res = await main.deleteOrganizationUser(user, uuid); - - if (res.status === 200) { - await getOrganizationUsers(); - await getOrganizationUsersCount(); - } else { - addToast('Error: could not delete user'); - } - } - }; - - const getBountyRoles = useCallback(async () => { - const roles = await main.getRoles(); - setBountyRoles(roles); - - const bountyRolesData = roles.map((role: any) => ({ - name: role.name, - status: false - })); - setBountyRolesData(bountyRolesData); - }, [main]); - - const getUserRoles = async (user: any) => { - if (uuid && user.owner_pubkey) { - const userRoles = await main.getUserRoles(uuid, user.owner_pubkey); - setUserRoles(userRoles); - - // set all values to false, so every user data will be fresh - const rolesData = bountyRolesData.map((data: any) => ({ name: data.name, status: false })); - - userRoles.forEach((userRole: any) => { - const index = rolesData.findIndex((role: any) => role.name === userRole.role); - rolesData[index]['status'] = true; - }); - - setBountyRolesData(rolesData); - } - }; - - const handleSettingsClick = async (user: any) => { - setUser(user); - setIsOpenRoles(true); - getUserRoles(user); - }; - - const closeHandler = () => { - setIsOpen(false); - }; - const closeRolesHandler = () => { - setIsOpenRoles(false); - }; - - const onSubmit = async (body: any) => { - setIsLoading(true); - - body.organization = uuid; + const getOrganizationUsersCount = useCallback(async () => { + if (uuid) { + const count = await main.getOrganizationUsersCount(uuid); + setUsersCount(count); + } + }, [main, uuid]); + + const getOrganizationUsers = useCallback(async () => { + if (uuid) { + const users = await main.getOrganizationUsers(uuid); + setUsers(users); + } + }, [main, uuid]); + + const deleteOrganizationUser = async (user: any) => { + if (uuid) { + const res = await main.deleteOrganizationUser(user, uuid); + + if (res.status === 200) { + await getOrganizationUsers(); + await getOrganizationUsersCount(); + } else { + addToast('Error: could not delete user', 'danger'); + } + } + }; + + const getBountyRoles = useCallback(async () => { + const roles = await main.getRoles(); + setBountyRoles(roles); + + const bountyRolesData = roles.map((role: any) => ({ + name: role.name, + status: false + })); + setBountyRolesData(bountyRolesData); + }, [main]) + + const getUserRoles = async (user: any) => { + if (uuid && user.owner_pubkey) { + const userRoles = await main.getUserRoles(uuid, user.owner_pubkey); + setUserRoles(userRoles); + + // set all values to false, so every user data will be fresh + const rolesData = bountyRolesData.map((data: any) => ({ name: data.name, status: false })); + + userRoles.forEach((userRole: any) => { + const index = rolesData.findIndex((role: any) => role.name === userRole.role); + rolesData[index]['status'] = true; + }); + + setBountyRolesData(rolesData); + } + }; + + const getOrganizationBudget = useCallback(async () => { + const organizationBudget = await main.getOrganizationBudget(uuid); + setOrgBudget(organizationBudget.total_budget); + }, [main]) + + const getPaymentsHistory = useCallback(async () => { + const paymentHistories = await main.getPaymentHistories(uuid); + setPaymentsHistory(paymentHistories); + }, [main]) + + const generateInvoice = async () => { + const token = ui.meInfo?.websocketToken; + if (token) { + const data = await main.getBudgetInvoice({ + amount: amount, + sender_pubkey: ui.meInfo?.owner_pubkey ?? '', + org_uuid: uuid, + websocket_token: token + }); + + setLnInvoice(data.response.invoice); + } + }; + + const handleSettingsClick = async (user: any) => { + setUser(user); + setIsOpenRoles(true); + getUserRoles(user); + }; + + const closeHandler = () => { + setIsOpen(false) + }; + + const closeRolesHandler = () => { + setIsOpenRoles(false) + }; + + const closeBudgetHandler = () => { + setIsOpenBudget(false) + }; + + const closeHistoryHandler = () => { + setIsOpenHistory(false) + }; + + const onSubmit = async (body: any) => { + setIsLoading(true); + + body.org_uuid = uuid; + + const res = await main.addOrganizationUser(body); + if (res.status === 200) { + await getOrganizationUsers(); + await getOrganizationUsersCount(); + } else { + addToast('Error: could not add user', 'danger'); + } + closeHandler(); + setIsLoading(false); + }; + + const roleChange = (e: any) => { + const rolesData = bountyRolesData.map((role: any) => { + if (role.name === e.target.value) { + role.status = !role.status + } + return role; + }); + + setBountyRolesData(rolesData); + }; + + const submitRoles = async () => { + const roleData = bountyRolesData.filter((r: any) => r.status).map((role: any) => ( + { + owner_pubkey: user?.owner_pubkey, + org_uuid: uuid, + role: role.name + } + )); + + if (uuid && user?.owner_pubkey) { + const res = await main.addUserRoles(roleData, uuid, user.owner_pubkey); + if (res.status === 200) { + await main.getUserRoles(uuid, user.owner_pubkey); + } else { + addToast('Error: could not add user roles', 'danger'); + } + setIsOpenRoles(false); + } + }; + + const onHandle = (event: any) => { + const res = JSON.parse(event.data); + if (res.msg === SOCKET_MSG.user_connect) { + const user = ui.meInfo; + if (user) { + user.websocketToken = res.body; + ui.setMeInfo(user); + } + } else if (res.msg === SOCKET_MSG.budget_success && res.invoice === main.lnInvoice) { + addToast('Budget was added successfully', 'success'); + setLnInvoice(''); + setInvoiceStatus(true); + main.setLnInvoice(''); + + // get new organization budget + getOrganizationBudget(); + closeBudgetHandler(); + } + }; + + useEffect(() => { + getOrganizationUsers(); + getOrganizationUsersCount(); + getBountyRoles(); + getOrganizationBudget(); + getPaymentsHistory(); + }, [ + getOrganizationUsers, + getOrganizationUsersCount, + getBountyRoles, + getOrganizationBudget, + getPaymentsHistory + ]); - const res = await main.addOrganizationUser(body); - if (res.status === 200) { - await getOrganizationUsers(); - await getOrganizationUsersCount(); - } else { - addToast('Error: could not add user'); - } - closeHandler(); - setIsLoading(false); - }; - - const roleChange = (e: any) => { - const rolesData = bountyRolesData.map((role: any) => { - if (role.name === e.target.value) { - role.status = !role.status; - } - return role; - }); - - setBountyRolesData(rolesData); - }; - - const submitRoles = async () => { - const roleData = bountyRolesData - .filter((r: any) => r.status) - .map((role: any) => ({ - owner_pubkey: user?.owner_pubkey, - organization: uuid, - role: role.name - })); - - if (uuid && user?.owner_pubkey) { - const res = await main.addUserRoles(roleData, uuid, user.owner_pubkey); - if (res.status === 200) { - await main.getUserRoles(uuid, user.owner_pubkey); - } else { - addToast('Error: could not add user roles'); - } - setIsOpenRoles(false); - } - }; - - useEffect(() => { - getOrganizationUsers(); - getOrganizationUsersCount(); - getBountyRoles(); - }, [getOrganizationUsers, getOrganizationUsersCount, getBountyRoles]); - - return ( - - props.close()} - icon={'arrow_back'} - style={{ - fontSize: 30, - marginLeft: 15, - cursor: 'pointer' - }} - /> - - - - {usersCount} User{usersCount > 1 && 's'} - - - {(isOrganizationAdmin || userHasRole(bountyRoles, userRoles, 'ADD USER')) && ( - setIsOpen(true)} - /> - )} - - - - Unique name - Public key - User actions - - {users.map((user: Person, i: number) => ( - - {user.unique_name} - {user.owner_pubkey} - - - {(isOrganizationAdmin || userHasRole(bountyRoles, userRoles, 'ADD ROLES')) && ( - handleSettingsClick(user)} - icon={'settings'} - style={{ - fontSize: 20, - marginLeft: 10, - cursor: 'pointer', - color: 'green' - }} - /> - )} - {(isOrganizationAdmin || userHasRole(bountyRoles, userRoles, 'DELETE USER')) && ( - { - deleteOrganizationUser(user); - }} - icon={'delete'} - style={{ - fontSize: 20, - marginLeft: 10, - cursor: 'pointer', - color: 'red' - }} - /> - )} - - - - ))} - - {isOpen && ( - - - {({ - setFieldTouched, - handleSubmit, - values, - setFieldValue, - errors, - initialValues - }: any) => ( - - Add new user -
- {schema.map((item: FormField) => ( - { - if (errors[item.name]) delete errors[item.name]; + useEffect(() => { + const socket: WebSocket = createSocketInstance(); + socket.onopen = () => { + console.log('Socket connected'); + }; + + socket.onmessage = (event: MessageEvent) => { + onHandle(event); + }; + + socket.onclose = () => { + console.log('Socket disconnected'); + }; + }, []); + + return ( + + props.close()} + icon={'arrow_back'} + style={{ + fontSize: 30, + marginLeft: 15, + cursor: 'pointer' + }} + /> + + + + User{usersCount > 1 && 's'} {usersCount} + {(isOrganizationAdmin || userHasRole(bountyRoles, userRoles, 'ADD USER')) && ( + setIsOpen(true)} + />) + } + + + Budget {orgBudget} sats + {(isOrganizationAdmin || userHasRole(bountyRoles, userRoles, 'ADD BUDGET')) && ( + setIsOpenBudget(true)} + />) + } + {(isOrganizationAdmin || userHasRole(bountyRoles, userRoles, 'VIEW REPORT')) && ( + setIsOpenHistory(true)}>View history + )} + + + + + + Unique name + Public key + User actions + + {users.map((user: Person, i: number) => ( + + {user.unique_name} + {user.owner_pubkey} + + + + {(isOrganizationAdmin || userHasRole(bountyRoles, userRoles, 'ADD ROLES')) && ( + handleSettingsClick(user)} + icon={'settings'} + style={{ + fontSize: 20, + marginLeft: 10, + cursor: 'pointer', + color: 'green', + }} + /> + )} + {(isOrganizationAdmin || userHasRole(bountyRoles, userRoles, 'DELETE USER')) && ( + { + deleteOrganizationUser(user) + }} + icon={'delete'} + style={{ + fontSize: 20, + marginLeft: 10, + cursor: 'pointer', + color: 'red', + }} + /> + )} + + + + + ))} + + {isOpen && ( + { - setFieldValue(item.name, e); + envStyle={{ + marginTop: isMobile ? 64 : 0, + background: color.pureWhite, + zIndex: 20, + ...(config?.modalStyle ?? {}), + maxHeight: '100%', + borderRadius: '10px' }} - setFieldValue={(e: any, f: any) => { - setFieldValue(e, f); + overlayClick={closeHandler} + bigCloseImage={closeHandler} + bigCloseImageStyle={{ + top: '-18px', + right: '-18px', + background: '#000', + borderRadius: '50%' }} - setFieldTouched={setFieldTouched} - handleBlur={() => setFieldTouched(item.name, false)} - handleFocus={() => setFieldTouched(item.name, true)} - setDisableFormButtons={setDisableFormButtons} - borderType={'bottom'} - imageIcon={true} - style={ - item.name === 'github_description' && !values.ticket_url - ? { - display: 'none' - } - : undefined - } - /> - ))} -
-
- )} -
-
- )} - {isOpenRoles && ( - - - Add user roles - - {bountyRolesData.map((role: any, i: number) => ( - - - {role.name} - - ))} - -