From 28651b516a3ea9da60dac88baba9d75b7c872612 Mon Sep 17 00:00:00 2001 From: Sahand Seifi Date: Fri, 24 Oct 2025 15:41:57 -0400 Subject: [PATCH 1/6] Update deps --- package-lock.json | 126 +++++++++++++++++++++++++--------------------- package.json | 4 +- 2 files changed, 72 insertions(+), 58 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1fc5868..6b6064b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@fontsource/roboto": "^5.1.1", "@mui/icons-material": "^6.3.1", "@mui/material": "^6.3.1", - "@notificationapi/core": "^0.0.17", + "@notificationapi/core": "^1.0.0", "javascript-time-ago": "^2.5.10", "liquidjs": "^10.14.0", "rc-virtual-list": "^3.11.5", @@ -206,13 +206,14 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" @@ -345,17 +346,19 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -370,24 +373,26 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", - "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.0" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", - "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.26.3" + "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" @@ -439,13 +444,14 @@ } }, "node_modules/@babel/template": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -469,12 +475,13 @@ } }, "node_modules/@babel/types": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", - "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1107,10 +1114,11 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1189,10 +1197,11 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1359,10 +1368,11 @@ } }, "node_modules/@microsoft/api-extractor/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1725,9 +1735,9 @@ } }, "node_modules/@notificationapi/core": { - "version": "0.0.17", - "resolved": "https://registry.npmjs.org/@notificationapi/core/-/core-0.0.17.tgz", - "integrity": "sha512-3T1qJVnBDMJisPDzEouYgbJz/Ao1RGbYC+NQ3KQ7nD6IHex5XZ25SSpVlxpavVJzFsxbWrXzKpFRkEdp8bGT1Q==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@notificationapi/core/-/core-1.0.0.tgz", + "integrity": "sha512-tcot6rxYX57V7qfd0kp/nhOHHLbDCFzQpOX37XSuzpm8N/PCaEO6b+Cr5hyrJVwIEnV4mCYObr2BUY5jHzovdg==" }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", @@ -2676,10 +2686,11 @@ "dev": true }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -3162,10 +3173,11 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4524,10 +4536,11 @@ } }, "node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4967,10 +4980,11 @@ } }, "node_modules/validator": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", - "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "version": "13.15.15", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz", + "integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.10" } diff --git a/package.json b/package.json index 3e1743f..972ad68 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ }, "devDependencies": { "@faker-js/faker": "^9.2.0", + "@playwright/test": "^1.56.1", "@types/node": "^20.12.7", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", @@ -48,7 +49,6 @@ "eslint-plugin-react-refresh": "^0.4.6", "faker-js": "^1.0.0", "glob": "^10.4.1", - "@playwright/test": "^1.56.1", "prettier": "^3.3.3", "typescript": "^5.2.2", "vite": "^5.4.21", @@ -67,7 +67,7 @@ "@fontsource/roboto": "^5.1.1", "@mui/icons-material": "^6.3.1", "@mui/material": "^6.3.1", - "@notificationapi/core": "^0.0.17", + "@notificationapi/core": "^1.0.0", "javascript-time-ago": "^2.5.10", "liquidjs": "^10.14.0", "rc-virtual-list": "^3.11.5", From 11a90a187978c7f78da3716daa17acbcb59ab8f2 Mon Sep 17 00:00:00 2001 From: Sahand Seifi Date: Sat, 25 Oct 2025 11:41:31 -0400 Subject: [PATCH 2/6] Update deps --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6b6064b..f9379be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@fontsource/roboto": "^5.1.1", "@mui/icons-material": "^6.3.1", "@mui/material": "^6.3.1", - "@notificationapi/core": "^1.0.0", + "@notificationapi/core": "^1.0.2", "javascript-time-ago": "^2.5.10", "liquidjs": "^10.14.0", "rc-virtual-list": "^3.11.5", @@ -1735,9 +1735,9 @@ } }, "node_modules/@notificationapi/core": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@notificationapi/core/-/core-1.0.0.tgz", - "integrity": "sha512-tcot6rxYX57V7qfd0kp/nhOHHLbDCFzQpOX37XSuzpm8N/PCaEO6b+Cr5hyrJVwIEnV4mCYObr2BUY5jHzovdg==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@notificationapi/core/-/core-1.0.2.tgz", + "integrity": "sha512-GvsY+uKRfvTUkAtjmSYQVSz/v9TSvZUHpo22Kfbaso4VZqzyp1WQIIHCrG1Vi9do7F6kEuaoeAGB3kusVdY1uA==" }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", diff --git a/package.json b/package.json index 972ad68..5ceb5e1 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "@fontsource/roboto": "^5.1.1", "@mui/icons-material": "^6.3.1", "@mui/material": "^6.3.1", - "@notificationapi/core": "^1.0.0", + "@notificationapi/core": "^1.0.2", "javascript-time-ago": "^2.5.10", "liquidjs": "^10.14.0", "rc-virtual-list": "^3.11.5", From 92262c2724e2b48c793661ab61e7187bd05cab3a Mon Sep 17 00:00:00 2001 From: Sahand Seifi Date: Sat, 25 Oct 2025 11:44:50 -0400 Subject: [PATCH 3/6] Slack connect --- lib/components/Slack/SlackConnect.tsx | 354 ++++++++++++++++++++++++++ lib/components/Slack/index.tsx | 1 + lib/main.ts | 1 + src/LiveComponents.tsx | 8 +- 4 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 lib/components/Slack/SlackConnect.tsx create mode 100644 lib/components/Slack/index.tsx diff --git a/lib/components/Slack/SlackConnect.tsx b/lib/components/Slack/SlackConnect.tsx new file mode 100644 index 0000000..53f2d59 --- /dev/null +++ b/lib/components/Slack/SlackConnect.tsx @@ -0,0 +1,354 @@ +import { useContext, useEffect, useState, useCallback } from 'react'; +import { + Box, + Button, + FormControl, + InputLabel, + MenuItem, + Select, + Typography, + CircularProgress, + Alert, + Stack +} from '@mui/material'; +import { NotificationAPIContext } from '../Provider/context'; +import { User } from '@notificationapi/core/dist/interfaces'; + +interface SlackChannel { + id: string; + name: string; + type: 'channel' | 'user'; +} + +interface SlackConnectProps { + description?: string; + connectButtonText?: string; + editButtonText?: string; + disconnectButtonText?: string; + saveButtonText?: string; + cancelButtonText?: string; + connectedText?: string; + selectChannelText?: string; +} + +export function SlackConnect({ + description = 'Connect your Slack workspace to receive notifications directly in Slack.', + connectButtonText = 'Connect Slack', + editButtonText = 'Edit Channel', + disconnectButtonText = 'Disconnect', + saveButtonText = 'Save', + cancelButtonText = 'Cancel', + connectedText = 'Slack notifications will be sent to:', + selectChannelText = 'Choose a channel or user to receive notifications:' +}: SlackConnectProps = {}) { + const context = useContext(NotificationAPIContext); + const [slackToken, setSlackToken] = useState< + User['slackToken'] | undefined + >(); + const [slackChannel, setSlackChannel] = useState(); + const [channels, setChannels] = useState([]); + const [selectedChannel, setSelectedChannel] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [isEditing, setIsEditing] = useState(false); + + const fetchUserSlackStatus = useCallback(async () => { + if (!context) return; + + try { + setLoading(true); + const client = context.getClient(); + + // Get user's current slack configuration using user.get + const user = await client.user.get(); + + if (user.slackToken) { + setSlackToken(user.slackToken); + } + + if (user.slackChannel) { + setSlackChannel(user.slackChannel); + } + } catch (err) { + console.error('Error fetching Slack status:', err); + // If the endpoint doesn't exist yet, that's okay + } finally { + setLoading(false); + } + }, [context]); + + const loadChannels = useCallback(async () => { + if (!context || !slackToken) return; + + try { + setLoading(true); + setError(null); + const client = context.getClient(); + + // Get channels and users from Slack + const response = await client.slack.getChannels(); + + // Combine channels and users into a single array + const allOptions: SlackChannel[] = [ + ...(response.channels || []) + .filter((c) => c.id && c.name) + .map((c) => ({ + id: c.id!, + name: c.name!, + type: 'channel' as const + })), + ...(response.users || []) + .filter((u) => u.id && u.name) + .map((u) => ({ + id: u.id!, + name: u.name!, + type: 'user' as const + })) + ]; + + setChannels(allOptions); + } catch (err) { + console.error('Error loading channels and users:', err); + setError('Failed to load Slack channels and users. Please try again.'); + } finally { + setLoading(false); + } + }, [context, slackToken]); + + useEffect(() => { + // Fetch the user's current slackToken and slackChannel from the API + fetchUserSlackStatus(); + }, [fetchUserSlackStatus]); + + useEffect(() => { + if (slackToken && !slackChannel && !isEditing) { + loadChannels(); + } + }, [slackToken, slackChannel, isEditing, loadChannels]); + + const handleConnectSlack = async () => { + if (!context) return; + + try { + setLoading(true); + setError(null); + const client = context.getClient(); + + // Generate Slack OAuth URL + const url = await client.slack.getOAuthUrl(); + + // Redirect to Slack OAuth + window.location.href = url; + } catch (err) { + console.error('Error connecting to Slack:', err); + setError('Failed to connect to Slack. Please try again.'); + } finally { + setLoading(false); + } + }; + + const handleSaveChannel = async () => { + if (!context || !selectedChannel) return; + + try { + setLoading(true); + setError(null); + const client = context.getClient(); + + // Set the selected channel + await client.slack.setChannel(selectedChannel); + + setSlackChannel(selectedChannel); + setIsEditing(false); + setError(null); + } catch (err) { + console.error('Error saving channel:', err); + setError('Failed to save channel. Please try again.'); + } finally { + setLoading(false); + } + }; + + const handleDisconnect = async () => { + if (!context) return; + + try { + setLoading(true); + setError(null); + const client = context.getClient(); + + // Remove slackToken and slackChannel using identify + await client.identify({ + // @ts-expect-error - null is not assignable to type string + slackToken: null, + // @ts-expect-error - null is not assignable to type string + slackChannel: null + }); + + setSlackToken(undefined); + setSlackChannel(undefined); + setSelectedChannel(''); + setChannels([]); + setIsEditing(false); + } catch (err) { + console.error('Error disconnecting Slack:', err); + setError('Failed to disconnect Slack. Please try again.'); + } finally { + setLoading(false); + } + }; + + const handleEdit = () => { + setIsEditing(true); + setSelectedChannel(slackChannel || ''); + if (channels.length === 0) { + loadChannels(); + } + }; + + const handleCancelEdit = () => { + setIsEditing(false); + setSelectedChannel(''); + }; + + if (!context) { + return null; + } + + // Show loading state + if (loading && !slackToken && !channels.length) { + return ( + + + + ); + } + + // No Slack token - show connect button + if (!slackToken) { + return ( + + {error && ( + + {error} + + )} + + + {description} + + + + + ); + } + + // Has token but no channel (or editing) + if (!slackChannel || isEditing) { + return ( + + {error && ( + + {error} + + )} + {loading ? ( + + + + ) : ( + + + {selectChannelText} + + + Channel or User + + + + {isEditing && ( + + )} + + + )} + + ); + } + + // Has both token and channel - show connected state + const selectedChannelInfo = channels.find((c) => c.id === slackChannel); + const channelName = selectedChannelInfo?.name || slackChannel; + const channelType = selectedChannelInfo?.type || 'channel'; + + return ( + + + {connectedText} + + + {channelType === 'channel' ? '#' : '@'} + {channelName} + + + + + ); +} diff --git a/lib/components/Slack/index.tsx b/lib/components/Slack/index.tsx new file mode 100644 index 0000000..a5a6b8e --- /dev/null +++ b/lib/components/Slack/index.tsx @@ -0,0 +1 @@ +export { SlackConnect } from './SlackConnect'; diff --git a/lib/main.ts b/lib/main.ts index 9d9bf26..fcd004d 100644 --- a/lib/main.ts +++ b/lib/main.ts @@ -14,6 +14,7 @@ export { NotificationPreferencesPopup } from './components/Preferences'; export { NotificationAPIProvider } from './components/Provider'; +export { SlackConnect } from './components/Slack'; // Debug utilities export { createDebugLogger, type DebugLogger } from './utils/debug'; diff --git a/src/LiveComponents.tsx b/src/LiveComponents.tsx index 6b81550..51793cd 100644 --- a/src/LiveComponents.tsx +++ b/src/LiveComponents.tsx @@ -6,7 +6,8 @@ import { NotificationCounter, NotificationAPIProvider, NotificationPreferencesPopup, - NotificationPreferencesInline + NotificationPreferencesInline, + SlackConnect } from '../lib/main'; import { Button, @@ -199,6 +200,11 @@ const LiveComponents: React.FC = ({

Preferences Inline:

+ + + +

Slack Connect:

+ From 3537486ac12441047f66a59daa721ca09cf13f2b Mon Sep 17 00:00:00 2001 From: Sahand Seifi Date: Sat, 25 Oct 2025 11:45:01 -0400 Subject: [PATCH 4/6] 1.5.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index f9379be..1bbbc91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@notificationapi/react", - "version": "1.4.1", + "version": "1.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@notificationapi/react", - "version": "1.4.1", + "version": "1.5.0", "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", diff --git a/package.json b/package.json index 5ceb5e1..f1c6b6a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@notificationapi/react", "private": false, - "version": "1.4.1", + "version": "1.5.0", "type": "module", "overrides": { "esbuild": "^0.25.0", From 4127818204b771cd0bfc266a6e8caabe7ff8d7f8 Mon Sep 17 00:00:00 2001 From: Sahand Seifi Date: Sat, 25 Oct 2025 11:57:23 -0400 Subject: [PATCH 5/6] Fix channel name preview --- lib/components/Slack/SlackConnect.tsx | 53 ++++++++++++++++++++------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/lib/components/Slack/SlackConnect.tsx b/lib/components/Slack/SlackConnect.tsx index 53f2d59..732ddd8 100644 --- a/lib/components/Slack/SlackConnect.tsx +++ b/lib/components/Slack/SlackConnect.tsx @@ -78,7 +78,7 @@ export function SlackConnect({ }, [context]); const loadChannels = useCallback(async () => { - if (!context || !slackToken) return; + if (!context || !slackToken) return []; try { setLoading(true); @@ -107,9 +107,11 @@ export function SlackConnect({ ]; setChannels(allOptions); + return allOptions; } catch (err) { console.error('Error loading channels and users:', err); setError('Failed to load Slack channels and users. Please try again.'); + return []; } finally { setLoading(false); } @@ -155,10 +157,22 @@ export function SlackConnect({ setError(null); const client = context.getClient(); - // Set the selected channel - await client.slack.setChannel(selectedChannel); + // Find the selected channel info to get its name and type + const channelInfo = channels.find((c) => c.id === selectedChannel); + if (!channelInfo) { + setError('Channel not found. Please try again.'); + return; + } + + // Format the channel as #channelname or @username + const formattedChannel = `${channelInfo.type === 'channel' ? '#' : '@'}${ + channelInfo.name + }`; + + // Set the selected channel with formatted name + await client.slack.setChannel(formattedChannel); - setSlackChannel(selectedChannel); + setSlackChannel(formattedChannel); setIsEditing(false); setError(null); } catch (err) { @@ -198,11 +212,29 @@ export function SlackConnect({ } }; - const handleEdit = () => { + const handleEdit = async () => { setIsEditing(true); - setSelectedChannel(slackChannel || ''); + + // Load channels if not already loaded + let channelsList = channels; if (channels.length === 0) { - loadChannels(); + channelsList = await loadChannels(); + } + + // Parse the slackChannel to find the matching channel ID + if (slackChannel) { + const isChannel = slackChannel.startsWith('#'); + const channelName = slackChannel.substring(1); // Remove # or @ + const channelType = isChannel ? 'channel' : 'user'; + + // Find the channel ID that matches the name and type + const matchingChannel = channelsList.find( + (c) => c.name === channelName && c.type === channelType + ); + + if (matchingChannel) { + setSelectedChannel(matchingChannel.id); + } } }; @@ -325,18 +357,13 @@ export function SlackConnect({ } // Has both token and channel - show connected state - const selectedChannelInfo = channels.find((c) => c.id === slackChannel); - const channelName = selectedChannelInfo?.name || slackChannel; - const channelType = selectedChannelInfo?.type || 'channel'; - return ( {connectedText} - {channelType === 'channel' ? '#' : '@'} - {channelName} + {slackChannel}