Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Member email search #1172

Merged
merged 5 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion app/handlers/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/getfider/fider/app/actions"
"github.com/getfider/fider/app/models/cmd"
"github.com/getfider/fider/app/models/entity"
"github.com/getfider/fider/app/models/query"
"github.com/getfider/fider/app/pkg/bus"
"github.com/getfider/fider/app/pkg/web"
Expand Down Expand Up @@ -126,11 +127,19 @@ func ManageMembers() web.HandlerFunc {
return c.Failure(err)
}

// Create an array of UserWithEmail structs from the allUsers.Result
allUsersWithEmail := make([]entity.UserWithEmail, len(allUsers.Result))
for i, user := range allUsers.Result {
allUsersWithEmail[i] = entity.UserWithEmail{
User: user,
}
}

return c.Page(http.StatusOK, web.Props{
Page: "Administration/pages/ManageMembers.page",
Title: "Manage Members · Site Settings",
Data: web.Map{
"users": allUsers.Result,
"users": allUsersWithEmail,
},
})
}
Expand Down
28 changes: 24 additions & 4 deletions app/models/entity/user.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package entity

import "github.com/getfider/fider/app/models/enum"
import (
"encoding/json"

//User represents an user inside our application
"github.com/getfider/fider/app/models/enum"
)

// User represents an user inside our application
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Expand All @@ -16,7 +20,7 @@ type User struct {
Status enum.UserStatus `json:"status"`
}

//HasProvider returns true if current user has registered with given provider
// HasProvider returns true if current user has registered with given provider
func (u *User) HasProvider(provider string) bool {
for _, p := range u.Providers {
if p.Name == provider {
Expand All @@ -36,8 +40,24 @@ func (u *User) IsAdministrator() bool {
return u.Role == enum.RoleAdministrator
}

//UserProvider represents the relationship between an User and an Authentication provide
// UserProvider represents the relationship between an User and an Authentication provide
type UserProvider struct {
Name string
UID string
}

// UserWithEmail is a wrapper around User that includes the email field when marshaling to JSON
type UserWithEmail struct {
*User
}

func (umc UserWithEmail) MarshalJSON() ([]byte, error) {
type Alias User // Prevent recursion
return json.Marshal(&struct {
*Alias
Email string `json:"email"`
}{
Alias: (*Alias)(umc.User),
Email: umc.User.Email,
})
}
55 changes: 55 additions & 0 deletions app/models/entity/user_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package entity_test

import (
"encoding/json"
"testing"

"github.com/getfider/fider/app/models/entity"
. "github.com/getfider/fider/app/pkg/assert"
)

func TestUserWithEmail_MarshalJSON(t *testing.T) {

RegisterT(t)
user := entity.UserWithEmail{
User: &entity.User{
ID: 1,
Name: "John Doe",
Email: "[email protected]",
Role: 1,
Status: 1,
},
}

expectedJSON := `{"id":1,"name":"John Doe","role":"visitor","status":"active","email":"[email protected]"}`

jsonData, err := json.Marshal(user)
if err != nil {
t.Errorf("Failed to marshal user to JSON: %v", err)
}

Expect(string(jsonData)).Equals(expectedJSON)

}

func TestUser_MarshalJSON(t *testing.T) {

RegisterT(t)
user := entity.User{
ID: 1,
Name: "John Doe",
Email: "[email protected]",
Role: 1,
Status: 1,
}

expectedJSON := `{"id":1,"name":"John Doe","role":"visitor","status":"active"}`

jsonData, err := json.Marshal(user)
if err != nil {
t.Errorf("Failed to marshal user to JSON: %v", err)
}

Expect(string(jsonData)).Equals(expectedJSON)

}
7 changes: 7 additions & 0 deletions public/components/common/UserName.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
display: inline-flex;
align-items: center;

&--email {
margin-left: 10px;
color: get("colors.gray.600");
font-size: get("font.size.xs");
font-weight: 400;
}

&--staff {
color: get("colors.primary.base");
border-color: get("colors.primary.base");
Expand Down
3 changes: 3 additions & 0 deletions public/components/common/UserName.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ interface UserNameProps {
id: number
name: string
role?: UserRole
email?: string
}
}

Expand All @@ -22,6 +23,8 @@ export const UserName = (props: UserNameProps) => {
return (
<div className={className}>
<span>{props.user.name || "Anonymous"}</span>
<>{props.user.email && <span className="c-username--email">({props.user.email})</span>}</>

{isStaff && (
<div data-tooltip={isStaff ? "Staff" : undefined}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
Expand Down
1 change: 1 addition & 0 deletions public/models/identity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export enum TenantStatus {
export interface User {
id: number
name: string
email?: string
role: UserRole
status: UserStatus
avatarURL: string
Expand Down
9 changes: 7 additions & 2 deletions public/pages/Administration/pages/ManageMembers.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export default class ManageMembersPage extends AdminBasePage<ManageMembersPagePr
super(props)

const users = this.props.users.sort(this.sortByStaff)

this.state = {
query: "",
users,
Expand All @@ -86,8 +87,12 @@ export default class ManageMembersPage extends AdminBasePage<ManageMembersPagePr
this.handleSearchFilterChanged("")
}

private memberFilter = (query: string, user: User): boolean => {
return user.name.toLowerCase().indexOf(query.toLowerCase()) >= 0 || (user.email && user.email.toLowerCase().indexOf(query.toLowerCase()) >= 0) || false
}

private handleSearchFilterChanged = (query: string) => {
const users = this.props.users.filter((x) => x.name.toLowerCase().indexOf(query.toLowerCase()) >= 0).sort(this.sortByStaff)
const users = this.props.users.filter((x) => this.memberFilter(query, x)).sort(this.sortByStaff)
this.setState({ query, users, visibleUsers: users.slice(0, 10) })
}

Expand Down Expand Up @@ -145,7 +150,7 @@ export default class ManageMembersPage extends AdminBasePage<ManageMembersPagePr
field="query"
icon={this.state.query ? IconX : IconSearch}
onIconClick={this.state.query ? this.clearSearch : undefined}
placeholder="Search for users by name..."
placeholder="Search for users by name / email ..."
value={this.state.query}
onChange={this.handleSearchFilterChanged}
/>
Expand Down
Loading