@ermis-network/ermis-chat-react 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/index.cjs +2410 -1308
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.css +471 -16
  4. package/dist/index.css.map +1 -1
  5. package/dist/index.d.mts +145 -1
  6. package/dist/index.d.ts +145 -1
  7. package/dist/index.mjs +2339 -1241
  8. package/dist/index.mjs.map +1 -1
  9. package/package.json +2 -2
  10. package/src/components/BannedOverlay.tsx +40 -0
  11. package/src/components/ChannelActions.tsx +231 -0
  12. package/src/components/ChannelHeader.tsx +38 -2
  13. package/src/components/ChannelInfo/ChannelInfo.tsx +118 -20
  14. package/src/components/ChannelInfo/ChannelInfoTabs.tsx +10 -2
  15. package/src/components/ChannelInfo/ChannelSettingsPanel.tsx +88 -1
  16. package/src/components/ChannelInfo/EditChannelModal.tsx +4 -4
  17. package/src/components/ChannelList.tsx +460 -38
  18. package/src/components/ClosedTopicOverlay.tsx +38 -0
  19. package/src/components/MessageInput.tsx +19 -2
  20. package/src/components/MessageItem.tsx +8 -11
  21. package/src/components/MessageQuickReactions.tsx +3 -2
  22. package/src/components/MessageReactions.tsx +8 -3
  23. package/src/components/MessageRenderers.tsx +7 -9
  24. package/src/components/PendingOverlay.tsx +41 -0
  25. package/src/components/TopicModal.tsx +189 -0
  26. package/src/components/VirtualMessageList.tsx +74 -43
  27. package/src/hooks/useBannedState.ts +27 -3
  28. package/src/hooks/useChannelCapabilities.ts +7 -3
  29. package/src/hooks/useChannelData.ts +1 -1
  30. package/src/hooks/useChannelListUpdates.ts +24 -3
  31. package/src/hooks/useChannelRowUpdates.ts +6 -0
  32. package/src/hooks/useMessageActions.ts +1 -1
  33. package/src/index.ts +6 -1
  34. package/src/styles/_channel-info.css +21 -0
  35. package/src/styles/_channel-list.css +217 -6
  36. package/src/styles/_message-bubble.css +75 -9
  37. package/src/styles/_message-input.css +24 -0
  38. package/src/styles/_message-list.css +51 -6
  39. package/src/styles/_message-quick-reactions.css +5 -0
  40. package/src/styles/_message-reactions.css +7 -0
  41. package/src/styles/_topic-modal.css +154 -0
  42. package/src/styles/index.css +1 -0
  43. package/src/types.ts +157 -3
@@ -1,5 +1,6 @@
1
1
  import React, { useState, useEffect } from 'react';
2
2
  import { Panel } from '../Panel';
3
+ import { useChatClient } from '../../hooks/useChatClient';
3
4
  import type { ChannelSettingsPanelProps } from '../../types';
4
5
 
5
6
  export const ChannelSettingsPanel: React.FC<ChannelSettingsPanelProps> = React.memo(({
@@ -16,9 +17,18 @@ export const ChannelSettingsPanel: React.FC<ChannelSettingsPanelProps> = React.m
16
17
  { label: '15m', value: 900000 },
17
18
  { label: '1h', value: 3600000 },
18
19
  ],
20
+ workspaceTopicsTitle = 'Workspace Topics',
21
+ topicsFeatureName = 'Topics',
22
+ topicsFeatureDescription = 'Enable sub-channels and discussions',
19
23
  }) => {
20
24
  // Config state
25
+ const { client } = useChatClient();
26
+ const currentUserId = client?.userID;
27
+ const currentUserRole = currentUserId ? channel?.state?.members?.[currentUserId]?.channel_role : undefined;
28
+ const isOwner = currentUserRole === 'owner';
29
+
21
30
  const [slowMode, setSlowMode] = useState<number>(0);
31
+ const [topicsEnabled, setTopicsEnabled] = useState<boolean>(false);
22
32
  const [capabilities, setCapabilities] = useState<Record<string, boolean>>({
23
33
  'send-message': true,
24
34
  'send-links': true,
@@ -43,6 +53,7 @@ export const ChannelSettingsPanel: React.FC<ChannelSettingsPanelProps> = React.m
43
53
  console.log('---syncData---', dataToSync);
44
54
  setSlowMode((dataToSync?.member_message_cooldown as number) || 0);
45
55
  setKeywords((dataToSync?.filter_words as string[]) || []);
56
+ setTopicsEnabled(dataToSync?.topics_enabled === true);
46
57
 
47
58
  const caps = dataToSync?.member_capabilities as string[] || [];
48
59
  setCapabilities({
@@ -86,6 +97,7 @@ export const ChannelSettingsPanel: React.FC<ChannelSettingsPanelProps> = React.m
86
97
 
87
98
  // Compute dirty state
88
99
  const isSlowModeChanged = slowMode !== ((channel?.data?.member_message_cooldown as number) || 0);
100
+ const isTopicsChanged = topicsEnabled !== (channel?.data?.topics_enabled === true);
89
101
 
90
102
  const currentKeywordsSorted = [...keywords].sort().join(',');
91
103
  const originalKeywordsSorted = [...((channel?.data?.filter_words as string[]) || [])].sort().join(',');
@@ -104,7 +116,7 @@ export const ChannelSettingsPanel: React.FC<ChannelSettingsPanelProps> = React.m
104
116
  };
105
117
  const isCapabilitiesChanged = Object.keys(capabilities).some(k => capabilities[k] !== initialCapabilities[k]);
106
118
 
107
- const isDirty = isSlowModeChanged || isKeywordsChanged || isCapabilitiesChanged;
119
+ const isDirty = isSlowModeChanged || isKeywordsChanged || isCapabilitiesChanged || isTopicsChanged;
108
120
 
109
121
  const handleAddNewKeyword = () => {
110
122
  if (newKeyword.trim()) {
@@ -176,6 +188,15 @@ export const ChannelSettingsPanel: React.FC<ChannelSettingsPanelProps> = React.m
176
188
  // Use _update instead of update to safely construct root-level payloads
177
189
  await (channel as any)._update(payload);
178
190
  }
191
+
192
+ if (isTopicsChanged) {
193
+ if (topicsEnabled) {
194
+ await channel.enableTopics();
195
+ } else {
196
+ await channel.disableTopics();
197
+ }
198
+ }
199
+
179
200
  onClose();
180
201
  } catch (err: any) {
181
202
  setError(err?.message || 'Failed to update settings');
@@ -422,6 +443,72 @@ export const ChannelSettingsPanel: React.FC<ChannelSettingsPanelProps> = React.m
422
443
  </div>
423
444
  </section>
424
445
 
446
+ {/* Section 3: Features */}
447
+ {(channel?.type === 'team' || channel?.type === 'meeting') && (
448
+ <section
449
+ className="ermis-settings-panel__section"
450
+ style={{
451
+ background: 'var(--ermis-bg-primary)',
452
+ padding: '16px',
453
+ borderRadius: '12px',
454
+ boxShadow: '0 2px 8px rgba(0,0,0,0.04)',
455
+ border: '1px solid var(--ermis-border-color)'
456
+ }}
457
+ >
458
+ <div style={{ display: 'flex', alignItems: 'center', marginBottom: '16px', gap: '8px' }}>
459
+ <div style={{ background: 'rgba(168, 85, 247, 0.1)', padding: '4px', borderRadius: '8px', color: '#a855f7' }}>
460
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
461
+ <polygon points="12 2 2 7 12 12 22 7 12 2"></polygon>
462
+ <polyline points="2 17 12 22 22 17"></polyline>
463
+ <polyline points="2 12 12 17 22 12"></polyline>
464
+ </svg>
465
+ </div>
466
+ <h4 style={{ fontSize: '14px', color: 'var(--ermis-text-primary)', fontWeight: 600, margin: 0 }}>
467
+ {workspaceTopicsTitle}
468
+ </h4>
469
+ </div>
470
+
471
+ <div className="ermis-settings-panel__toggles" style={{ display: 'flex', flexDirection: 'column' }}>
472
+ <div style={{ background: 'var(--ermis-bg-secondary)', borderRadius: '8px', overflow: 'hidden', border: '1px solid var(--ermis-border-color)' }}>
473
+ <div
474
+ style={{
475
+ display: 'flex',
476
+ alignItems: 'center',
477
+ justifyContent: 'space-between',
478
+ padding: '10px 14px',
479
+ }}
480
+ >
481
+ <div style={{ display: 'flex', flexDirection: 'column' }}>
482
+ <span style={{ fontSize: '14px', fontWeight: 500, color: 'var(--ermis-text-primary)' }}>
483
+ {topicsFeatureName}
484
+ </span>
485
+ <span style={{ fontSize: '12px', color: 'var(--ermis-text-secondary)', marginTop: '2px' }}>
486
+ {topicsFeatureDescription}
487
+ </span>
488
+ {!isOwner && (
489
+ <span style={{ fontSize: '11px', color: 'var(--ermis-color-danger)', marginTop: '4px', fontWeight: 500 }}>
490
+ Only channel owner can change this.
491
+ </span>
492
+ )}
493
+ </div>
494
+ <button
495
+ type="button"
496
+ role="switch"
497
+ aria-checked={topicsEnabled}
498
+ className={`ermis-channel-info__edit-toggle ${topicsEnabled ? 'ermis-channel-info__edit-toggle--on' : ''}`}
499
+ onClick={() => isOwner && setTopicsEnabled(!topicsEnabled)}
500
+ disabled={isSaving || !isOwner}
501
+ style={{ transform: 'scale(0.85)', transformOrigin: 'right center', cursor: (!isOwner || isSaving) ? 'not-allowed' : 'pointer' }}
502
+ >
503
+ <span className="ermis-channel-info__edit-toggle-thumb" />
504
+ </button>
505
+ </div>
506
+ </div>
507
+ </div>
508
+ </section>
509
+ )}
510
+
511
+
425
512
  </div>
426
513
 
427
514
  {/* Footer Area */}
@@ -28,7 +28,7 @@ export const EditChannelModal: React.FC<EditChannelModalProps> = React.memo(({
28
28
  const originalImage = (channel.data?.image as string) || '';
29
29
  const originalDescription = (channel.data?.description as string) || '';
30
30
  const originalPublic = Boolean(channel.data?.public);
31
- const isTeamChannel = channel.type === 'team';
31
+ const isTeamOrMeetingChannel = channel.type === 'team' || channel.type === 'meeting';
32
32
 
33
33
  // Form state
34
34
  const [name, setName] = useState(originalName);
@@ -92,7 +92,7 @@ export const EditChannelModal: React.FC<EditChannelModalProps> = React.memo(({
92
92
  payload.description = description.trim();
93
93
  hasChanges = true;
94
94
  }
95
- if (isTeamChannel && isPublic !== originalPublic) {
95
+ if (isTeamOrMeetingChannel && isPublic !== originalPublic) {
96
96
  payload.public = isPublic;
97
97
  hasChanges = true;
98
98
  }
@@ -102,7 +102,7 @@ export const EditChannelModal: React.FC<EditChannelModalProps> = React.memo(({
102
102
  }
103
103
 
104
104
  return hasChanges ? payload : null;
105
- }, [name, description, isPublic, selectedFile, originalName, originalDescription, originalPublic, isTeamChannel]);
105
+ }, [name, description, isPublic, selectedFile, originalName, originalDescription, originalPublic, isTeamOrMeetingChannel]);
106
106
 
107
107
  const handleSave = useCallback(async () => {
108
108
  const payload = buildPayload();
@@ -237,7 +237,7 @@ export const EditChannelModal: React.FC<EditChannelModalProps> = React.memo(({
237
237
  </div>
238
238
 
239
239
  {/* Public toggle — only for team channels */}
240
- {isTeamChannel && (
240
+ {isTeamOrMeetingChannel && (
241
241
  <div className="ermis-channel-info__edit-field ermis-channel-info__edit-field--toggle">
242
242
  <label className="ermis-channel-info__edit-label">{publicLabel}</label>
243
243
  <button