diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 3fe935097..811327def 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -34,12 +34,12 @@ export const getPostHogProjectApiKey = () => infisical.get('POSTHOG_PROJECT_API_ export const getSentryDSN = () => infisical.get('SENTRY_DSN')!; export const getSiteURL = () => infisical.get('SITE_URL')!; export const getSmtpHost = () => infisical.get('SMTP_HOST')!; -export const getSmtpSecure = () => infisical.get('SMTP_SECURE')! === 'true' || false; +export const getSmtpSecure = () => infisical.get('SMTP_SECURE')! === 'true' || false; export const getSmtpPort = () => parseInt(infisical.get('SMTP_PORT')!) || 587; export const getSmtpUsername = () => infisical.get('SMTP_USERNAME')!; export const getSmtpPassword = () => infisical.get('SMTP_PASSWORD')!; export const getSmtpFromAddress = () => infisical.get('SMTP_FROM_ADDRESS')!; -export const getSmtpFromName = () => infisical.get('SMTP_FROM_NAME')! || 'Infisical'; +export const getSmtpFromName = () => infisical.get('SMTP_FROM_NAME')! || 'Infisical'; export const getStripeProductStarter = () => infisical.get('STRIPE_PRODUCT_STARTER')!; export const getStripeProductPro = () => infisical.get('STRIPE_PRODUCT_PRO')!; export const getStripeProductTeam = () => infisical.get('STRIPE_PRODUCT_TEAM')!; @@ -47,4 +47,5 @@ export const getStripePublishableKey = () => infisical.get('STRIPE_PUBLISHABLE_K export const getStripeSecretKey = () => infisical.get('STRIPE_SECRET_KEY')!; export const getStripeWebhookSecret = () => infisical.get('STRIPE_WEBHOOK_SECRET')!; export const getTelemetryEnabled = () => infisical.get('TELEMETRY_ENABLED')! !== 'false' && true; -export const getLoopsApiKey = () => infisical.get('LOOPS_API_KEY')!; \ No newline at end of file +export const getLoopsApiKey = () => infisical.get('LOOPS_API_KEY')!; +export const getSmtpConfigured = () => infisical.get('SMTP_HOST') == '' || infisical.get('SMTP_HOST') == undefined ? false : true \ No newline at end of file diff --git a/backend/src/controllers/v1/signupController.ts b/backend/src/controllers/v1/signupController.ts index cb411a29e..00ed05ede 100644 --- a/backend/src/controllers/v1/signupController.ts +++ b/backend/src/controllers/v1/signupController.ts @@ -7,7 +7,7 @@ import { } from '../../helpers/signup'; import { createToken } from '../../helpers/auth'; import { BadRequestError } from '../../utils/errors'; -import { getInviteOnlySignup, getJwtSignupLifetime, getJwtSignupSecret } from '../../config'; +import { getInviteOnlySignup, getJwtSignupLifetime, getJwtSignupSecret, getSmtpConfigured } from '../../config'; /** * Signup step 1: Initialize account for user under email [email] and send a verification code @@ -21,7 +21,7 @@ export const beginEmailSignup = async (req: Request, res: Response) => { try { email = req.body.email; - if (getInviteOnlySignup() || false) { + if (getInviteOnlySignup()) { // Only one user can create an account without being invited. The rest need to be invited in order to make an account const userCount = await User.countDocuments({}) if (userCount != 0) { @@ -75,10 +75,12 @@ export const verifyEmailSignup = async (req: Request, res: Response) => { } // verify email - await checkEmailVerification({ - email, - code - }); + if (getSmtpConfigured()) { + await checkEmailVerification({ + email, + code + }); + } if (!user) { user = await new User({ diff --git a/backend/src/controllers/v2/authController.ts b/backend/src/controllers/v2/authController.ts index b0204e1b9..3a36d559c 100644 --- a/backend/src/controllers/v2/authController.ts +++ b/backend/src/controllers/v2/authController.ts @@ -87,7 +87,7 @@ export const login1 = async (req: Request, res: Response) => { */ export const login2 = async (req: Request, res: Response) => { try { - + if (!req.headers['user-agent']) throw InternalServerError({ message: 'User-Agent header is required' }); const { email, clientProof } = req.body; @@ -127,12 +127,12 @@ export const login2 = async (req: Request, res: Response) => { expiresIn: getJwtMfaLifetime(), secret: getJwtMfaSecret() }); - + const code = await TokenService.createToken({ type: TOKEN_EMAIL_MFA, email }); - + // send MFA code [code] to [email] await sendMail({ template: 'emailMfa.handlebars', @@ -142,13 +142,13 @@ export const login2 = async (req: Request, res: Response) => { code } }); - + return res.status(200).send({ mfaEnabled: true, token }); } - + await checkUserDevice({ user, ip: req.ip, @@ -181,7 +181,7 @@ export const login2 = async (req: Request, res: Response) => { iv?: string; tag?: string; } - + const response: ResponseData = { mfaEnabled: false, encryptionVersion: user.encryptionVersion, @@ -191,7 +191,7 @@ export const login2 = async (req: Request, res: Response) => { iv: user.iv, tag: user.tag } - + if ( user?.protectedKey && user?.protectedKeyIV && @@ -206,14 +206,14 @@ export const login2 = async (req: Request, res: Response) => { name: ACTION_LOGIN, userId: user._id }); - + loginAction && await EELogService.createLog({ userId: user._id, actions: [loginAction], channel: getChannelFromUserAgent(req.headers['user-agent']), ipAddress: req.ip }); - + return res.status(200).send(response); } @@ -244,7 +244,7 @@ export const sendMfaToken = async (req: Request, res: Response) => { type: TOKEN_EMAIL_MFA, email }); - + // send MFA code [code] to [email] await sendMail({ template: 'emailMfa.handlebars', @@ -259,9 +259,9 @@ export const sendMfaToken = async (req: Request, res: Response) => { Sentry.captureException(err); return res.status(400).send({ message: 'Failed to send MFA code' - }); + }); } - + return res.status(200).send({ message: 'Successfully sent new MFA code' }); @@ -274,75 +274,87 @@ export const sendMfaToken = async (req: Request, res: Response) => { * @param res */ export const verifyMfaToken = async (req: Request, res: Response) => { - const { email, mfaToken } = req.body; + const { email, mfaToken } = req.body; - await TokenService.validateToken({ - type: TOKEN_EMAIL_MFA, - email, - token: mfaToken - }); + await TokenService.validateToken({ + type: TOKEN_EMAIL_MFA, + email, + token: mfaToken + }); - const user = await User.findOne({ - email - }).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag'); + const user = await User.findOne({ + email + }).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag'); - if (!user) throw new Error('Failed to find user'); + if (!user) throw new Error('Failed to find user'); - await checkUserDevice({ - user, - ip: req.ip, - userAgent: req.headers['user-agent'] ?? '' - }); + await checkUserDevice({ + user, + ip: req.ip, + userAgent: req.headers['user-agent'] ?? '' + }); - // issue tokens - const tokens = await issueAuthTokens({ userId: user._id.toString() }); + // issue tokens + const tokens = await issueAuthTokens({ userId: user._id.toString() }); - // store (refresh) token in httpOnly cookie - res.cookie('jid', tokens.refreshToken, { - httpOnly: true, - path: '/', - sameSite: 'strict', - secure: getNodeEnv() === 'production' ? true : false - }); - - interface VerifyMfaTokenRes { - encryptionVersion: number; - protectedKey?: string; - protectedKeyIV?: string; - protectedKeyTag?: string; - token: string; - publicKey: string; - encryptedPrivateKey: string; - iv: string; - tag: string; - } + // store (refresh) token in httpOnly cookie + res.cookie('jid', tokens.refreshToken, { + httpOnly: true, + path: '/', + sameSite: 'strict', + secure: getNodeEnv() === 'production' ? true : false + }); - const resObj: VerifyMfaTokenRes = { - encryptionVersion: user.encryptionVersion, - token: tokens.token, - publicKey: user.publicKey as string, - encryptedPrivateKey: user.encryptedPrivateKey as string, - iv: user.iv as string, - tag: user.tag as string - } - - if (user?.protectedKey && user?.protectedKeyIV && user?.protectedKeyTag) { - resObj.protectedKey = user.protectedKey; - resObj.protectedKeyIV = user.protectedKeyIV; - resObj.protectedKeyTag = user.protectedKeyTag; - } + interface VerifyMfaTokenRes { + encryptionVersion: number; + protectedKey?: string; + protectedKeyIV?: string; + protectedKeyTag?: string; + token: string; + publicKey: string; + encryptedPrivateKey: string; + iv: string; + tag: string; + } - const loginAction = await EELogService.createAction({ - name: ACTION_LOGIN, - userId: user._id - }); - - loginAction && await EELogService.createLog({ - userId: user._id, - actions: [loginAction], - channel: getChannelFromUserAgent(req.headers['user-agent']), - ipAddress: req.ip - }); + interface VerifyMfaTokenRes { + encryptionVersion: number; + protectedKey?: string; + protectedKeyIV?: string; + protectedKeyTag?: string; + token: string; + publicKey: string; + encryptedPrivateKey: string; + iv: string; + tag: string; + } - return res.status(200).send(resObj); -} \ No newline at end of file + const resObj: VerifyMfaTokenRes = { + encryptionVersion: user.encryptionVersion, + token: tokens.token, + publicKey: user.publicKey as string, + encryptedPrivateKey: user.encryptedPrivateKey as string, + iv: user.iv as string, + tag: user.tag as string + } + + if (user?.protectedKey && user?.protectedKeyIV && user?.protectedKeyTag) { + resObj.protectedKey = user.protectedKey; + resObj.protectedKeyIV = user.protectedKeyIV; + resObj.protectedKeyTag = user.protectedKeyTag; + } + + const loginAction = await EELogService.createAction({ + name: ACTION_LOGIN, + userId: user._id + }); + + loginAction && await EELogService.createLog({ + userId: user._id, + actions: [loginAction], + channel: getChannelFromUserAgent(req.headers['user-agent']), + ipAddress: req.ip + }); + + return res.status(200).send(resObj); +} diff --git a/backend/src/helpers/nodemailer.ts b/backend/src/helpers/nodemailer.ts index d765f3200..fe7c05044 100644 --- a/backend/src/helpers/nodemailer.ts +++ b/backend/src/helpers/nodemailer.ts @@ -3,7 +3,7 @@ import fs from 'fs'; import path from 'path'; import handlebars from 'handlebars'; import nodemailer from 'nodemailer'; -import { getSmtpFromName, getSmtpFromAddress } from '../config'; +import { getSmtpFromName, getSmtpFromAddress, getSmtpConfigured } from '../config'; let smtpTransporter: nodemailer.Transporter; @@ -25,23 +25,25 @@ const sendMail = async ({ recipients: string[]; substitutions: any; }) => { - try { - const html = fs.readFileSync( - path.resolve(__dirname, '../templates/' + template), - 'utf8' - ); - const temp = handlebars.compile(html); - const htmlToSend = temp(substitutions); + if (getSmtpConfigured()) { + try { + const html = fs.readFileSync( + path.resolve(__dirname, '../templates/' + template), + 'utf8' + ); + const temp = handlebars.compile(html); + const htmlToSend = temp(substitutions); - await smtpTransporter.sendMail({ - from: `"${getSmtpFromName()}" <${getSmtpFromAddress()}>`, - to: recipients.join(', '), - subject: subjectLine, - html: htmlToSend - }); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); + await smtpTransporter.sendMail({ + from: `"${getSmtpFromName()}" <${getSmtpFromAddress()}>`, + to: recipients.join(', '), + subject: subjectLine, + html: htmlToSend + }); + } catch (err) { + Sentry.setUser(null); + Sentry.captureException(err); + } } }; diff --git a/backend/src/index.ts b/backend/src/index.ts index 64d58cfc5..03534a927 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -23,49 +23,49 @@ const swaggerFile = require('../spec.json'); const requestIp = require('request-ip'); import { apiLimiter } from './helpers/rateLimiter'; import { - workspace as eeWorkspaceRouter, - secret as eeSecretRouter, - secretSnapshot as eeSecretSnapshotRouter, - action as eeActionRouter + workspace as eeWorkspaceRouter, + secret as eeSecretRouter, + secretSnapshot as eeSecretSnapshotRouter, + action as eeActionRouter } from './ee/routes/v1'; import { - signup as v1SignupRouter, - auth as v1AuthRouter, - bot as v1BotRouter, - organization as v1OrganizationRouter, - workspace as v1WorkspaceRouter, - membershipOrg as v1MembershipOrgRouter, - membership as v1MembershipRouter, - key as v1KeyRouter, - inviteOrg as v1InviteOrgRouter, - user as v1UserRouter, - userAction as v1UserActionRouter, - secret as v1SecretRouter, - serviceToken as v1ServiceTokenRouter, - password as v1PasswordRouter, - stripe as v1StripeRouter, - integration as v1IntegrationRouter, - integrationAuth as v1IntegrationAuthRouter + signup as v1SignupRouter, + auth as v1AuthRouter, + bot as v1BotRouter, + organization as v1OrganizationRouter, + workspace as v1WorkspaceRouter, + membershipOrg as v1MembershipOrgRouter, + membership as v1MembershipRouter, + key as v1KeyRouter, + inviteOrg as v1InviteOrgRouter, + user as v1UserRouter, + userAction as v1UserActionRouter, + secret as v1SecretRouter, + serviceToken as v1ServiceTokenRouter, + password as v1PasswordRouter, + stripe as v1StripeRouter, + integration as v1IntegrationRouter, + integrationAuth as v1IntegrationAuthRouter } from './routes/v1'; import { - signup as v2SignupRouter, - auth as v2AuthRouter, - users as v2UsersRouter, - organizations as v2OrganizationsRouter, - workspace as v2WorkspaceRouter, - secret as v2SecretRouter, // begin to phase out - secrets as v2SecretsRouter, - serviceTokenData as v2ServiceTokenDataRouter, - apiKeyData as v2APIKeyDataRouter, - environment as v2EnvironmentRouter, - tags as v2TagsRouter, + signup as v2SignupRouter, + auth as v2AuthRouter, + users as v2UsersRouter, + organizations as v2OrganizationsRouter, + workspace as v2WorkspaceRouter, + secret as v2SecretRouter, // begin to phase out + secrets as v2SecretsRouter, + serviceTokenData as v2ServiceTokenDataRouter, + apiKeyData as v2APIKeyDataRouter, + environment as v2EnvironmentRouter, + tags as v2TagsRouter, } from './routes/v2'; import { healthCheck } from './routes/status'; import { getLogger } from './utils/logger'; import { RouteNotFoundError } from './utils/errors'; import { requestErrorHandler } from './middleware/requestErrorHandler'; import { - getMongoURL, + getMongoURL, getNodeEnv, getPort, getSentryDSN, @@ -73,10 +73,12 @@ import { } from './config'; const main = async () => { - await infisical.connect({ - token: process.env.INFISICAL_TOKEN! - }); - + if (process.env.INFISICAL_TOKEN != "" || process.env.INFISICAL_TOKEN != undefined) { + await infisical.connect({ + token: process.env.INFISICAL_TOKEN! + }); + } + logTelemetryMessage(); setTransporter(initSmtp()); @@ -158,7 +160,7 @@ const main = async () => { //* Handle unrouted requests and respond with proper error message as well as status code app.use((req, res, next) => { - if (res.headersSent) return next(); + if (res.headersSent) return next(); next(RouteNotFoundError({ message: `The requested source '(${req.method})${req.url}' was not found` })) }) @@ -170,7 +172,7 @@ const main = async () => { createTestUserForDevelopment(); setUpHealthEndpoint(server); - + server.on('close', async () => { await DatabaseService.closeDatabase(); }) diff --git a/backend/src/routes/status/status.ts b/backend/src/routes/status/status.ts index 4ab82cca9..f793128be 100644 --- a/backend/src/routes/status/status.ts +++ b/backend/src/routes/status/status.ts @@ -1,4 +1,5 @@ import express, { Request, Response } from 'express'; +import { getSmtpConfigured } from '../../config'; const router = express.Router(); @@ -8,6 +9,7 @@ router.get( res.status(200).json({ date: new Date(), message: 'Ok', + emailConfigured: getSmtpConfigured() }) } ); diff --git a/frontend/src/components/signup/TeamInviteStep.tsx b/frontend/src/components/signup/TeamInviteStep.tsx index 29cb95076..12d558606 100644 --- a/frontend/src/components/signup/TeamInviteStep.tsx +++ b/frontend/src/components/signup/TeamInviteStep.tsx @@ -2,10 +2,13 @@ import React, { useState } from 'react'; import { useRouter } from 'next/router'; import { useTranslation } from 'next-i18next'; +import { useFetchServerStatus } from '@app/hooks/api/serverDetails'; +import { usePopUp } from '@app/hooks/usePopUp'; import addUserToOrg from '@app/pages/api/organization/addUserToOrg'; import getWorkspaces from '@app/pages/api/workspace/getWorkspaces'; import Button from '../basic/buttons/Button'; +import { EmailServiceSetupModal } from '../v2'; /** * This is the last step of the signup flow. People can optionally invite their teammates here. @@ -14,6 +17,10 @@ export default function TeamInviteStep(): JSX.Element { const [emails, setEmails] = useState(''); const { t } = useTranslation(); const router = useRouter(); + const {data: serverDetails } = useFetchServerStatus() + const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp([ + 'setUpEmail' + ] as const); // Redirect user to the getting started page const redirectToHome = async () => { @@ -62,10 +69,20 @@ export default function TeamInviteStep(): JSX.Element { + + + +); diff --git a/frontend/src/components/v2/EmailServiceSetupModal/index.tsx b/frontend/src/components/v2/EmailServiceSetupModal/index.tsx new file mode 100644 index 000000000..33a7d1854 --- /dev/null +++ b/frontend/src/components/v2/EmailServiceSetupModal/index.tsx @@ -0,0 +1 @@ +export { EmailServiceSetupModal } from './EmailServiceSetupModal'; diff --git a/frontend/src/components/v2/index.tsx b/frontend/src/components/v2/index.tsx index 52c8fc4f4..130b797ae 100644 --- a/frontend/src/components/v2/index.tsx +++ b/frontend/src/components/v2/index.tsx @@ -3,6 +3,7 @@ export * from './Card'; export * from './Checkbox'; export * from './DeleteActionModal'; export * from './Dropdown'; +export * from './EmailServiceSetupModal' export * from './EmptyState'; export * from './FormControl'; export * from './IconButton'; diff --git a/frontend/src/hooks/api/serverDetails/index.ts b/frontend/src/hooks/api/serverDetails/index.ts new file mode 100644 index 000000000..812293a1d --- /dev/null +++ b/frontend/src/hooks/api/serverDetails/index.ts @@ -0,0 +1 @@ +export { useFetchServerStatus } from './queries' \ No newline at end of file diff --git a/frontend/src/hooks/api/serverDetails/queries.tsx b/frontend/src/hooks/api/serverDetails/queries.tsx new file mode 100644 index 000000000..29abea127 --- /dev/null +++ b/frontend/src/hooks/api/serverDetails/queries.tsx @@ -0,0 +1,19 @@ +import {useQuery } from '@tanstack/react-query'; + +import { apiRequest } from '@app/config/request'; + +import { ServerStatus } from './types'; + +// cache key +const serverStatusKeys = { + serverStatus: ['serverStatus'] as const +}; + +const fetchServerStatus = async () => { + const {data} = await apiRequest.get('/api/status'); + return data; +}; + +export const useFetchServerStatus= () => { + return useQuery({ queryKey: serverStatusKeys.serverStatus, queryFn: fetchServerStatus }); +} \ No newline at end of file diff --git a/frontend/src/hooks/api/serverDetails/types.ts b/frontend/src/hooks/api/serverDetails/types.ts new file mode 100644 index 000000000..0deb90a38 --- /dev/null +++ b/frontend/src/hooks/api/serverDetails/types.ts @@ -0,0 +1,5 @@ +export type ServerStatus = { + date: string; + message: string; + emailConfigured: boolean; +}; \ No newline at end of file diff --git a/frontend/src/pages/signup.tsx b/frontend/src/pages/signup.tsx index 62d0f4a47..298fc9536 100644 --- a/frontend/src/pages/signup.tsx +++ b/frontend/src/pages/signup.tsx @@ -13,6 +13,7 @@ import TeamInviteStep from '@app/components/signup/TeamInviteStep'; import UserInfoStep from '@app/components/signup/UserInfoStep'; import SecurityClient from '@app/components/utilities/SecurityClient'; import { getTranslatedStaticProps } from '@app/components/utilities/withTranslateProps'; +import { useFetchServerStatus } from '@app/hooks/api/serverDetails'; import checkEmailVerificationCode from './api/auth/CheckEmailVerificationCode'; import getWorkspaces from './api/workspace/getWorkspaces'; @@ -25,10 +26,12 @@ export default function SignUp() { const [password, setPassword] = useState(''); const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); - const [code, setCode] = useState(''); + const [code, setCode] = useState('123456'); const [codeError, setCodeError] = useState(false); const [step, setStep] = useState(1); const router = useRouter(); + const {data: serverDetails } = useFetchServerStatus() + const { t } = useTranslation(); @@ -68,6 +71,13 @@ export default function SignUp() { } }; + // when email service is not configured, skip step 2 + useEffect(() => { + if (!serverDetails?.emailConfigured && step === 2){ + incrementStep() + } + }, [step]); + return (
diff --git a/frontend/src/pages/verify-email.tsx b/frontend/src/pages/verify-email.tsx index 3b7e1c8d5..203b040bf 100644 --- a/frontend/src/pages/verify-email.tsx +++ b/frontend/src/pages/verify-email.tsx @@ -6,12 +6,19 @@ import Link from 'next/link'; import Button from '@app/components/basic/buttons/Button'; import InputField from '@app/components/basic/InputField'; import { getTranslatedStaticProps } from '@app/components/utilities/withTranslateProps'; +import { EmailServiceSetupModal } from '@app/components/v2'; +import { usePopUp } from '@app/hooks'; +import { useFetchServerStatus } from '@app/hooks/api/serverDetails'; import SendEmailOnPasswordReset from './api/auth/SendEmailOnPasswordReset'; export default function VerifyEmail() { const [email, setEmail] = useState(''); const [step, setStep] = useState(1); + const {data: serverDetails } = useFetchServerStatus() + const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp([ + 'setUpEmail' + ] as const); /** * This function sends the verification email and forwards a user to the next step. @@ -63,7 +70,13 @@ export default function VerifyEmail() {
-
@@ -80,6 +93,11 @@ export default function VerifyEmail() { )} + + handlePopUpToggle('setUpEmail', isOpen)} + /> ); } diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsTable/OrgIncidentContactsTable.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsTable/OrgIncidentContactsTable.tsx index c3bd55f51..e118e455b 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsTable/OrgIncidentContactsTable.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsTable/OrgIncidentContactsTable.tsx @@ -13,6 +13,7 @@ import * as yup from 'yup'; import { Button, DeleteActionModal, + EmailServiceSetupModal, EmptyState, FormControl, IconButton, @@ -28,6 +29,7 @@ import { THead, Tr} from '@app/components/v2'; import { usePopUp } from '@app/hooks'; +import { useFetchServerStatus } from '@app/hooks/api/serverDetails'; import { IncidentContact } from '@app/hooks/api/types'; type Props = { @@ -50,9 +52,11 @@ export const OrgIncidentContactsTable = ({ isLoading }: Props) => { const [searchContact, setSearchContact] = useState(''); + const {data: serverDetails } = useFetchServerStatus() const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ 'addContact', - 'removeContact' + 'removeContact', + 'setUpEmail' ] as const); const { @@ -92,7 +96,13 @@ export const OrgIncidentContactsTable = ({
@@ -180,6 +190,10 @@ export const OrgIncidentContactsTable = ({ onChange={(isOpen) => handlePopUpToggle('removeContact', isOpen)} onDeleteApproved={onRemoveIncidentContact} /> + handlePopUpToggle('setUpEmail', isOpen)} + />
); }; diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgMembersTable/OrgMembersTable.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgMembersTable/OrgMembersTable.tsx index 9bed5954b..b127abeb9 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgMembersTable/OrgMembersTable.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgMembersTable/OrgMembersTable.tsx @@ -9,7 +9,7 @@ import * as yup from 'yup'; import { Button, DeleteActionModal, - EmptyState, + EmailServiceSetupModal, EmptyState, FormControl, IconButton, Input, @@ -28,6 +28,7 @@ import { Tr, UpgradePlanModal} from '@app/components/v2'; import { usePopUp } from '@app/hooks'; +import { useFetchServerStatus } from '@app/hooks/api/serverDetails'; import { OrgUser, Workspace } from '@app/hooks/api/types'; type Props = { @@ -64,10 +65,12 @@ export const OrgMembersTable = ({ }: Props) => { const router = useRouter(); const [searchMemberFilter, setSearchMemberFilter] = useState(''); + const {data: serverDetails } = useFetchServerStatus() const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ 'addMember', 'removeMember', - 'upgradePlan' + 'upgradePlan', + 'setUpEmail' ] as const); const { @@ -79,8 +82,8 @@ export const OrgMembersTable = ({ const onAddMember = async ({ email }: TAddMemberForm) => { await onInviteMember(email); - handlePopUpClose('addMember'); - reset(); + handlePopUpClose('addMember'); + reset(); }; const onRemoveOrgMemberApproved = async () => { @@ -121,10 +124,14 @@ export const OrgMembersTable = ({