Skip to content

Commit 927a334

Browse files
authored
Merge pull request #2077 from The-Commit-Company/manage-channels
feat: Manage Channels settings page and Create Channel
2 parents 7f6bf65 + a572351 commit 927a334

25 files changed

Lines changed: 601 additions & 247 deletions

apps/web/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import ForgotPassword from "@pages/auth/ForgotPassword"
2323
import WorkspaceList from "@pages/settings/Workspaces/WorkspaceList"
2424
import { SearchLayout } from "@components/layout/SearchLayout"
2525
import CustomEmojiList from "@pages/settings/CustomEmojiList/CustomEmojiList"
26+
import { ManageChannels } from "@pages/settings/Channels/ManageChannels"
2627

2728
const isDesktop = window.innerWidth > 768
2829

@@ -64,6 +65,7 @@ function App() {
6465
<Route path="appearance" element={<Appearance />} />
6566
<Route path="preferences" element={<Preferences />} />
6667
<Route path="workspaces" element={<WorkspaceList />} />
68+
<Route path="channels" element={<ManageChannels />} />
6769
<Route path="emojis" element={<CustomEmojiList />} />
6870
</Route>
6971
<Route path="notifications" element={<Notifications />} />

apps/web/src/components/channel-sidebar/ChannelSidebar.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import { useLocalStorage } from 'usehooks-ts'
2727
import { CustomizeSidebarButton } from "@components/features/channel/CustomizeSidebar/CustomizeSidebarButton"
2828
import { useChannels } from "@hooks/useChannels"
2929
import useCurrentRavenUser from "@raven/lib/hooks/useCurrentRavenUser"
30+
import { useParams } from "react-router"
31+
import { useState } from "react"
3032

3133

3234
interface ChannelSidebarProps {
@@ -47,8 +49,9 @@ export function ChannelSidebar({
4749

4850
const { channels } = useChannels()
4951
const { myProfile } = useCurrentRavenUser()
50-
const channelSidebarData = useGroupedChannels(channels, myProfile)
51-
52+
const { workspaceID } = useParams()
53+
const [showMyChannelsOnly, setShowMyChannelsOnly] = useState(false)
54+
const channelSidebarData = useGroupedChannels(channels, myProfile, workspaceID, showMyChannelsOnly)
5255

5356
// Calculate total unread count for a group
5457
const getGroupUnreadCount = (channels: ChannelListItem[]) => {
@@ -91,7 +94,7 @@ export function ChannelSidebar({
9194
</TooltipProvider>
9295
</div>
9396
<div className="flex items-center gap-1">
94-
<CustomizeSidebarButton />
97+
<CustomizeSidebarButton showMyChannelsOnly={showMyChannelsOnly} setShowMyChannelsOnly={setShowMyChannelsOnly} />
9598
<CreateChannelButton />
9699
</div>
97100
</div>

apps/web/src/components/features/channel/CreateChannel/AddMembersStep.tsx

Lines changed: 67 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -3,114 +3,42 @@ import { Button } from '@components/ui/button'
33
import { Input } from '@components/ui/input'
44
import { UserAvatar } from '@components/features/message/UserAvatar'
55
import { Search, UserPlus, X } from 'lucide-react'
6-
import type { RavenUser } from '@raven/types/Raven/RavenUser'
76
import type { UserFields } from '@raven/types/common/UserFields'
87
import { ScrollArea } from '@components/ui/scroll-area'
8+
import { db, UserData } from '../../../../db/db'
9+
import { useLiveQuery } from 'dexie-react-hooks';
10+
import { useDebounce } from '@raven/lib/hooks/useDebounce';
11+
import _ from '@lib/translate'
12+
import useCurrentRavenUser from "@raven/lib/hooks/useCurrentRavenUser"
913

1014
interface AddMembersStepProps {
11-
selectedUsers: RavenUser[]
12-
onSelectUsers: (users: RavenUser[]) => void
15+
selectedUsers: UserData[]
16+
onSelectUsers: (users: UserData[]) => void
1317
}
1418

1519
export const AddMembersStep = ({ selectedUsers, onSelectUsers }: AddMembersStepProps) => {
20+
21+
const { myProfile } = useCurrentRavenUser()
1622
const [searchQuery, setSearchQuery] = useState('')
23+
const debouncedText = useDebounce(searchQuery, 200)
24+
const filterText = searchQuery === '' ? '' : debouncedText
25+
26+
const filteredUsers = useLiveQuery(() => db.users
27+
.where('enabled')
28+
.equals(1)
29+
.and((user) => user.name !== myProfile?.name)
30+
.and((user) => user.name.toLowerCase().includes(filterText.toLowerCase()) || user.full_name.toLowerCase().includes(filterText.toLowerCase()))
31+
.and((user) => !selectedUsers.some((selected) => selected.name === user.name))
32+
.toArray(),
33+
[filterText, selectedUsers])
34+
1735
const [announcement, setAnnouncement] = useState('')
1836
const searchInputRef = useRef<HTMLInputElement>(null)
1937

20-
// TODO: Replace with actual data from API
21-
const mockAvailableUsers: RavenUser[] = [
22-
{
23-
name: 'michael.brown@company.com',
24-
full_name: 'Michael Brown',
25-
user_image:
26-
'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=150&h=150&fit=crop&crop=face',
27-
type: 'User',
28-
availability_status: 'Available',
29-
custom_status: '',
30-
enabled: 1,
31-
first_name: 'Michael',
32-
creation: '2024-01-01',
33-
modified: '2024-01-01',
34-
owner: 'admin',
35-
modified_by: 'admin',
36-
docstatus: 0,
37-
},
38-
{
39-
name: 'emily.davis@company.com',
40-
full_name: 'Emily Davis',
41-
user_image:
42-
'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=150&h=150&fit=crop&crop=face',
43-
type: 'User',
44-
availability_status: 'Away',
45-
custom_status: 'On lunch break',
46-
enabled: 1,
47-
first_name: 'Emily',
48-
creation: '2024-01-01',
49-
modified: '2024-01-01',
50-
owner: 'admin',
51-
modified_by: 'admin',
52-
docstatus: 0,
53-
},
54-
{
55-
name: 'daniel.wilson@company.com',
56-
full_name: 'Daniel Wilson',
57-
user_image:
58-
'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=150&h=150&fit=crop&crop=face',
59-
type: 'User',
60-
availability_status: 'Available',
61-
custom_status: '',
62-
enabled: 1,
63-
first_name: 'Daniel',
64-
creation: '2024-01-01',
65-
modified: '2024-01-01',
66-
owner: 'admin',
67-
modified_by: 'admin',
68-
docstatus: 0,
69-
},
70-
{
71-
name: 'olivia.moore@company.com',
72-
full_name: 'Olivia Moore',
73-
user_image:
74-
'https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=150&h=150&fit=crop&crop=face',
75-
type: 'User',
76-
availability_status: 'Do not disturb',
77-
custom_status: 'In a call',
78-
enabled: 1,
79-
first_name: 'Olivia',
80-
creation: '2024-01-01',
81-
modified: '2024-01-01',
82-
owner: 'admin',
83-
modified_by: 'admin',
84-
docstatus: 0,
85-
},
86-
{
87-
name: 'david.lee@company.com',
88-
full_name: 'David Lee',
89-
user_image: undefined,
90-
type: 'User',
91-
availability_status: 'Available',
92-
custom_status: '',
93-
enabled: 1,
94-
first_name: 'David',
95-
creation: '2024-01-01',
96-
modified: '2024-01-01',
97-
owner: 'admin',
98-
modified_by: 'admin',
99-
docstatus: 0,
100-
},
101-
]
102-
103-
const filteredUsers = mockAvailableUsers.filter(
104-
(user) =>
105-
!selectedUsers.some((selected) => selected.name === user.name) &&
106-
(user.full_name.toLowerCase().includes(searchQuery.toLowerCase()) ||
107-
user.name.toLowerCase().includes(searchQuery.toLowerCase()))
108-
)
109-
110-
const handleSelectUser = (user: RavenUser) => {
38+
const handleSelectUser = (user: UserData) => {
11139
onSelectUsers([...selectedUsers, user])
11240
setSearchQuery('')
113-
setAnnouncement(`${user.full_name} added to channel`)
41+
setAnnouncement(_(`${user.full_name} added to channel`))
11442
// Return focus to search input after selection
11543
setTimeout(() => {
11644
searchInputRef.current?.focus()
@@ -125,7 +53,7 @@ export const AddMembersStep = ({ selectedUsers, onSelectUsers }: AddMembersStepP
12553
}
12654
}
12755

128-
const handleKeyDownOnUser = (e: React.KeyboardEvent, user: RavenUser) => {
56+
const handleKeyDownOnUser = (e: React.KeyboardEvent, user: UserData) => {
12957
if (e.key === 'Enter' || e.key === ' ') {
13058
e.preventDefault()
13159
handleSelectUser(user)
@@ -140,7 +68,7 @@ export const AddMembersStep = ({ selectedUsers, onSelectUsers }: AddMembersStepP
14068
}
14169

14270
return (
143-
<div className="space-y-5 h-full overflow-y-auto px-6">
71+
<div className="flex flex-col h-full min-h-0 px-6 gap-4">
14472
{/* Screen reader announcements */}
14573
<div
14674
role="status"
@@ -152,84 +80,87 @@ export const AddMembersStep = ({ selectedUsers, onSelectUsers }: AddMembersStepP
15280
</div>
15381

15482
{/* Description */}
155-
<div className="text-sm text-muted-foreground" id="search-description">
156-
Search and select team members to add to this channel. You can skip this step and invite members later.
83+
<div className="text-sm text-muted-foreground shrink-0" id="search-description">
84+
{_('Search and select team members to add to this channel. You can skip this step and invite members later.')}
15785
</div>
15886

15987
{/* Selected Users as Badges */}
16088
{selectedUsers.length > 0 && (
161-
<div className="space-y-2">
162-
<div className="flex items-center justify-between">
89+
<div className="space-y-2 shrink-0 min-h-0 flex flex-col">
90+
<div className="flex items-center justify-between shrink-0">
16391
<span className="text-xs font-medium text-muted-foreground">
164-
Selected ({selectedUsers.length})
92+
{_('Selected')} ({selectedUsers.length})
16593
</span>
16694
<Button
16795
variant="ghost"
16896
size="sm"
16997
onClick={() => {
17098
onSelectUsers([])
171-
setAnnouncement('All members removed from selection')
99+
setAnnouncement(_('All members removed from selection'))
172100
}}
173101
className="h-6 text-xs px-3"
174102
type="button"
175103
aria-label={`Clear all ${selectedUsers.length} selected members`}
176104
>
177-
Clear all
105+
{_('Clear all')}
178106
</Button>
179107
</div>
180-
<div className="flex flex-wrap gap-2" role="list" aria-label="Selected members">
181-
{selectedUsers.map((user) => (
182-
<button
183-
key={user.name}
184-
type="button"
185-
className="flex items-center gap-1 px-2 py-1 cursor-pointer hover:bg-muted transition-colors rounded-md bg-secondary text-secondary-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
186-
onClick={() => handleRemoveSelectedUser(user.name)}
187-
onKeyDown={(e) => handleKeyDownOnBadge(e, user.name)}
188-
aria-label={`Remove ${user.full_name} from selection`}
189-
role="listitem"
190-
>
191-
<span className="text-xs">{user.full_name}</span>
192-
<X className="h-3 w-3" aria-hidden="true" />
193-
</button>
194-
))}
195-
</div>
108+
<ScrollArea className="max-h-15 min-h-0 shrink-0">
109+
<div className="flex flex-wrap gap-2 p-0.5" role="list" aria-label="Selected members">
110+
{selectedUsers.map((user) => (
111+
<button
112+
key={user.name}
113+
type="button"
114+
className="flex items-center gap-1 px-2 py-1 cursor-pointer hover:bg-muted transition-colors rounded-md bg-secondary text-secondary-foreground focus:shadow-md"
115+
onClick={() => handleRemoveSelectedUser(user.name)}
116+
onKeyDown={(e) => handleKeyDownOnBadge(e, user.name)}
117+
aria-label={_(`Remove ${user.full_name} from selection`)}
118+
role="listitem"
119+
>
120+
<span className="text-xs">{user.full_name}</span>
121+
<X className="h-3 w-3" aria-hidden="true" />
122+
</button>
123+
))}
124+
</div>
125+
</ScrollArea>
196126
</div>
197127
)}
198128

199129
{/* Search */}
200-
<div>
130+
<div className="shrink-0">
201131
<div className="relative">
202132
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" aria-hidden="true" />
203133
<Input
204134
ref={searchInputRef}
205-
placeholder="Search by name or email..."
135+
placeholder={_('Search by name or email...')}
206136
value={searchQuery}
207137
onChange={(e) => setSearchQuery(e.target.value)}
208138
className="pl-9"
209139
type="text"
210-
aria-label="Search team members"
140+
aria-label={_('Search team members')}
211141
aria-describedby="search-description"
212142
/>
213143
</div>
214144
</div>
215145

216146
{/* Available Users */}
217-
<ScrollArea className="h-[320px]">
218-
<div className="space-y-0.5 pr-4" role="list" aria-label="Available team members">
219-
{filteredUsers.map((user) => (
220-
<button
147+
<ScrollArea className="flex-1 min-h-10">
148+
<div className="space-y-1 p-0.5" role="list" aria-label="Available team members">
149+
{filteredUsers?.map((user) => (
150+
<Button
221151
key={user.name}
222152
type="button"
223-
className="flex items-center gap-2.5 px-2 py-2 rounded-md hover:bg-accent/50 transition-colors cursor-pointer group w-full text-left focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1"
153+
variant='ghost'
154+
className="flex items-center gap-2.5 hover:bg-accent/50 transition-colors cursor-pointer group w-full text-left h-12"
224155
onClick={() => handleSelectUser(user)}
225156
onKeyDown={(e) => handleKeyDownOnUser(e, user)}
226-
aria-label={`Add ${user.full_name} (${user.name}) to channel`}
157+
aria-label={_(`Add ${user.full_name} (${user.name}) to channel`)}
227158
role="listitem"
228159
>
229160
<UserAvatar
230161
user={user as UserFields}
231162
size="sm"
232-
className="flex-shrink-0"
163+
className="shrink-0"
233164
showStatusIndicator={false}
234165
/>
235166
<div className="flex-1 min-w-0">
@@ -240,23 +171,23 @@ export const AddMembersStep = ({ selectedUsers, onSelectUsers }: AddMembersStepP
240171
{user.name}
241172
</div>
242173
</div>
243-
<UserPlus className="h-3.5 w-3.5 text-muted-foreground/60 group-hover:text-muted-foreground flex-shrink-0 transition-colors" aria-hidden="true" />
244-
</button>
174+
<UserPlus className="h-3.5 w-3.5 text-muted-foreground/60 group-hover:text-muted-foreground shrink-0 transition-colors mr-2" aria-hidden="true" />
175+
</Button>
245176
))}
246177
</div>
247178

248-
{filteredUsers.length === 0 && searchQuery && (
179+
{filteredUsers?.length === 0 && filterText && (
249180
<div className="text-center py-8 pr-4">
250181
<p className="text-sm text-muted-foreground">
251-
No users found matching your search.
182+
{_('No users found matching your search.')}
252183
</p>
253184
</div>
254185
)}
255186

256-
{filteredUsers.length === 0 && !searchQuery && selectedUsers.length > 0 && (
187+
{filteredUsers?.length === 0 && !filterText && selectedUsers.length > 0 && (
257188
<div className="text-center py-8 pr-4">
258189
<p className="text-sm text-muted-foreground">
259-
All available users have been selected.
190+
{_('All available users have been selected.')}
260191
</p>
261192
</div>
262193
)}

0 commit comments

Comments
 (0)