Resolve merge conflicts

This commit is contained in:
Tuan Dang
2023-03-28 14:43:56 +07:00
43 changed files with 1088 additions and 325 deletions
@@ -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 {
</div>
<Button
text={t('signup:step5-send-invites') ?? ''}
onButtonPressed={() => inviteUsers({ emails })}
onButtonPressed={() => {
if(serverDetails?.emailConfigured){
inviteUsers({ emails })
}else{
handlePopUpOpen('setUpEmail');
}
}}
size="lg"
/>
</div>
<EmailServiceSetupModal
isOpen={popUp.setUpEmail?.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle('setUpEmail', isOpen)}
/>
</div>
);
}
@@ -4,13 +4,11 @@ const POSTHOG_HOST =
process.env.NEXT_PUBLIC_POSTHOG_HOST! || "https://app.posthog.com";
const STRIPE_PRODUCT_PRO = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_PRO!;
const STRIPE_PRODUCT_STARTER = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_STARTER!;
const SITE_URL = "https://app.infisical.com";
export {
ENV,
POSTHOG_API_KEY,
POSTHOG_HOST,
SITE_URL,
STRIPE_PRODUCT_PRO,
STRIPE_PRODUCT_STARTER
};
@@ -1,7 +1,5 @@
import { jsPDF } from 'jspdf';
import { SITE_URL } from './config';
interface PDFProps {
personalName: string;
personalEmail: string;
@@ -19,7 +17,7 @@ const yyyy = today.getFullYear();
const todayFormatted = `${mm}/${dd}/${yyyy}`;
function createPdfHeader(doc: jsPDF, personalName : string) {
function createPdfHeader(doc: jsPDF, personalName: string) {
doc.setFillColor(255, 255, 255);
doc.rect(0, 0, 600, 900, 'F');
doc.setTextColor(23, 23, 23);
@@ -30,6 +28,10 @@ function createPdfHeader(doc: jsPDF, personalName : string) {
}
function createPdfContent(doc: jsPDF, personalEmail: string, generatedKey: string) {
const { protocol, hostname, port } = window.location;
const portSuffix = port && port !== '80' ? `:${port}` : '';
const siteURL = `${protocol}//${hostname}${portSuffix}`;
doc.setFontSize(14);
doc.text(
'In case you get locked out of you Infisical account, you`ll need these account details',
@@ -73,7 +75,7 @@ function createPdfContent(doc: jsPDF, personalEmail: string, generatedKey: strin
doc.roundedRect(170, 488, 375, 35, 5, 5, 'F');
doc.setTextColor(23, 23, 23);
doc.setFontSize(14);
doc.text(`${SITE_URL}/login`, 180, 420);
doc.text(`${siteURL}/login`, 180, 420);
doc.text(personalEmail, 180, 465);
doc.text(generatedKey, 180, 510);
doc.text('Need help? Contact us at [email protected]', 32, 575);
@@ -0,0 +1,21 @@
import { Button } from '../Button';
import { Modal, ModalContent } from '../Modal';
type Props = {
isOpen?: boolean;
onOpenChange?: (isOpen: boolean) => void;
};
export const EmailServiceSetupModal = ({ isOpen, onOpenChange }: Props): JSX.Element => (
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent title="Email service not configured">
<p className="mb-4 text-bunker-300">
The administrators of this Infisical instance have not yet set up an email service provider required to perform this action
</p>
<a href="https://infisical.com/docs/self-hosting/configuration/email">
<Button className="mr-4">Learn more</Button>
</a>
</ModalContent>
</Modal>
);
@@ -0,0 +1 @@
export { EmailServiceSetupModal } from './EmailServiceSetupModal';
+1
View File
@@ -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';
@@ -0,0 +1 @@
export { useFetchServerStatus } from './queries'
@@ -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<ServerStatus>('/api/status');
return data;
};
export const useFetchServerStatus= () => {
return useQuery({ queryKey: serverStatusKeys.serverStatus, queryFn: fetchServerStatus });
}
@@ -0,0 +1,5 @@
export type ServerStatus = {
date: string;
message: string;
emailConfigured: boolean;
};
+7 -1
View File
@@ -83,8 +83,14 @@ export const useAddUserToWs = () => {
export const useAddUserToOrg = () => {
const queryClient = useQueryClient();
type Response = {
data: {
message: string,
completeInviteLink: string | undefined
}
}
return useMutation<{}, {}, AddUserToOrgDTO>({
return useMutation<Response, {}, AddUserToOrgDTO>({
mutationFn: (dto) => apiRequest.post(`/api/v1/invite-org/signup`, dto),
onSuccess: (_, { organizationId }) => {
queryClient.invalidateQueries(userKeys.getOrgUsers(organizationId));
+18 -4
View File
@@ -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,19 @@ export default function SignUp() {
}
};
// when email service is not configured, skip step 2 and 5
useEffect(() => {
if (!serverDetails?.emailConfigured && step === 2){
incrementStep()
}
if (!serverDetails?.emailConfigured && step === 5){
getWorkspaces().then((userWorkspaces)=>{
router.push(`/dashboard/${userWorkspaces[0]._id}`);
});
}
}, [step]);
return (
<div className="bg-bunker-800 h-screen flex flex-col items-center justify-center">
<Head>
@@ -111,9 +127,7 @@ export default function SignUp() {
password={password}
name={`${firstName} ${lastName}`}
/>
) : (
<TeamInviteStep />
)}
) : (serverDetails?.emailConfigured ? <TeamInviteStep /> : "")}
</form>
</div>
</div>
+19 -1
View File
@@ -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() {
</div>
<div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm">
<div className="text-l mt-6 m-8 px-8 py-3 text-lg">
<Button text="Continue" onButtonPressed={sendVerificationEmail} size="lg" />
<Button text="Continue" onButtonPressed={()=>{
if (serverDetails?.emailConfigured){
sendVerificationEmail()
} else {
handlePopUpOpen('setUpEmail');
}
}} size="lg" />
</div>
</div>
</div>
@@ -80,6 +93,11 @@ export default function VerifyEmail() {
</div>
</div>
)}
<EmailServiceSetupModal
isOpen={popUp.setUpEmail?.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle('setUpEmail', isOpen)}
/>
</div>
);
}
@@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { useState } from 'react';
import { useTranslation } from 'next-i18next';
import { plans } from 'public/data/frequentConstants';
@@ -55,6 +56,8 @@ export const OrgSettingsPage = () => {
const addIncidentContact = useAddIncidentContact();
const removeIncidentContact = useDeleteIncidentContact();
const [completeInviteLink, setcompleteInviteLink] = useState<string|undefined>("")
const isMoreUsersNotAllowed =
(orgUsers || []).length >= 5 &&
subscriptionPlan === plans.starter &&
@@ -100,11 +103,16 @@ export const OrgSettingsPage = () => {
if (!currentOrg?._id) return;
try {
await addUserToOrg.mutateAsync({ organizationId: currentOrg?._id, inviteeEmail: email });
createNotification({
text: 'Successfully invited user to the organization.',
type: 'success'
});
const {data} = await addUserToOrg.mutateAsync({ organizationId: currentOrg?._id, inviteeEmail: email });
setcompleteInviteLink(data?.completeInviteLink)
// only show this notification when email is configured. A [completeInviteLink] will not be sent if smtp is configured
if (!data.completeInviteLink){
createNotification({
text: 'Successfully invited user to the organization.',
type: 'success'
});
}
} catch (error) {
console.error(error);
createNotification({
@@ -249,6 +257,8 @@ export const OrgSettingsPage = () => {
onRemoveMember={onRemoveUserOrgMembership}
onRoleChange={onUpdateOrgUserRole}
onGrantAccess={onGrantUserAccess}
completeInviteLink={completeInviteLink}
setCompleteInviteLink={setcompleteInviteLink}
/>
</div>
<div className="mb-6 mt-2 w-full rounded-md bg-white/5 p-6">
@@ -13,6 +13,7 @@ import * as yup from 'yup';
import {
Button,
DeleteActionModal,
EmailServiceSetupModal,
EmptyState,
FormControl,
IconButton,
@@ -29,6 +30,7 @@ import {
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 = {
@@ -51,9 +53,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 {
@@ -93,7 +97,13 @@ export const OrgIncidentContactsTable = ({
<div>
<Button
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen('addContact')}
onClick={() => {
if (serverDetails?.emailConfigured){
handlePopUpOpen('addContact');
} else {
handlePopUpOpen('setUpEmail');
}
}}
>
Add Contact
</Button>
@@ -181,6 +191,10 @@ export const OrgIncidentContactsTable = ({
onChange={(isOpen) => handlePopUpToggle('removeContact', isOpen)}
onDeleteApproved={onRemoveIncidentContact}
/>
<EmailServiceSetupModal
isOpen={popUp.setUpEmail?.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle('setUpEmail', isOpen)}
/>
</div>
);
};
@@ -1,7 +1,7 @@
import { useMemo, useState } from 'react';
import { Dispatch, SetStateAction, useMemo, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useRouter } from 'next/router';
import { faMagnifyingGlass, faPlus, faTrash, faUsers } from '@fortawesome/free-solid-svg-icons';
import { faCheck, faCopy, faMagnifyingGlass, faPlus, faTrash, faUsers } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';
@@ -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 = {
@@ -42,6 +43,8 @@ type Props = {
onGrantAccess: (userId: string, publicKey: string) => Promise<void>;
// the current user id to block remove org button
userId: string;
completeInviteLink: string | undefined,
setCompleteInviteLink: Dispatch<SetStateAction<string | undefined>>
};
const addMemberFormSchema = yup.object({
@@ -60,14 +63,18 @@ export const OrgMembersTable = ({
onGrantAccess,
onRoleChange,
userId,
isLoading
isLoading,
completeInviteLink,
setCompleteInviteLink
}: 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 +86,11 @@ export const OrgMembersTable = ({
const onAddMember = async ({ email }: TAddMemberForm) => {
await onInviteMember(email);
handlePopUpClose('addMember');
reset();
if (serverDetails?.emailConfigured){
handlePopUpClose('addMember');
}
reset();
};
const onRemoveOrgMemberApproved = async () => {
@@ -106,6 +116,11 @@ export const OrgMembersTable = ({
[members, searchMemberFilter]
);
const copyTokenToClipboard = () => {
navigator.clipboard.writeText(completeInviteLink as string);
// setIsTokenCopied.on();
};
return (
<div className="w-full">
<div className="mb-4 flex">
@@ -171,7 +186,7 @@ export const OrgMembersTable = ({
<SelectItem value="member">member</SelectItem>
</Select>
)}
{(status === 'invited' || status === 'verified') && (
{((status === 'invited' || status === 'verified') && serverDetails?.emailConfigured) && (
<Button className='w-40' colorSchema="secondary" onClick={() => onInviteMember(email)}>
Resend Invite
</Button>
@@ -232,20 +247,23 @@ export const OrgMembersTable = ({
isOpen={popUp?.addMember?.isOpen}
onOpenChange={(isOpen) => {
handlePopUpToggle('addMember', isOpen);
reset();
setCompleteInviteLink(undefined)
}}
>
<ModalContent
title={`Invite others to ${orgName}`}
subTitle={
<>
An invite is specific to an email address and expires after 1 day.
<br />
For security reasons, you will need to separately add members to projects.
</>
<div>
{!completeInviteLink && <div>
An invite is specific to an email address and expires after 1 day.
<br />
For security reasons, you will need to separately add members to projects.
</div>}
{completeInviteLink && "This Infisical instance does not have a email provider setup. Please share this invite link with the invitee manually"}
</div>
}
>
<form onSubmit={handleSubmit(onAddMember)}>
{!completeInviteLink && <form onSubmit={handleSubmit(onAddMember)} >
<Controller
control={control}
defaultValue=""
@@ -274,7 +292,22 @@ export const OrgMembersTable = ({
Cancel
</Button>
</div>
</form>
</form>}
{
completeInviteLink &&
<div className="mt-2 mb-3 mr-2 flex items-center justify-end rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
<p className="mr-4 break-all">{completeInviteLink}</p>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={copyTokenToClipboard}
>
<FontAwesomeIcon icon={false ? faCheck : faCopy} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">click to copy</span>
</IconButton>
</div>
}
</ModalContent>
</Modal>
<DeleteActionModal
@@ -289,6 +322,10 @@ export const OrgMembersTable = ({
onOpenChange={(isOpen) => handlePopUpToggle('upgradePlan', isOpen)}
text="You can add custom environments if you switch to Infisical's Team plan."
/>
<EmailServiceSetupModal
isOpen={popUp.setUpEmail?.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle('setUpEmail', isOpen)}
/>
</div>
);
};
@@ -1,7 +1,9 @@
import { useEffect, useState } from 'react';
import { useNotificationContext } from '@app/components/context/Notifications/NotificationProvider';
import { Checkbox } from '@app/components/v2';
import { Checkbox, EmailServiceSetupModal } from '@app/components/v2';
import { useFetchServerStatus } from '@app/hooks/api/serverDetails';
import { usePopUp } from '@app/hooks/usePopUp';
import { useGetUser } from '../../../../hooks/api';
import { User } from '../../../../hooks/api/types';
@@ -11,6 +13,11 @@ export const SecuritySection = () => {
const [isMfaEnabled, setIsMfaEnabled] = useState(false);
const { data: user } = useGetUser();
const { createNotification } = useNotificationContext();
const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp([
'setUpEmail'
] as const);
const {data: serverDetails } = useFetchServerStatus()
useEffect(() => {
if (user && typeof user.isMfaEnabled !== 'undefined') {
@@ -42,6 +49,7 @@ export const SecuritySection = () => {
}
return (
<>
<form>
<div className="mb-6 mt-2 flex w-full flex-col items-start rounded-md bg-white/5 px-6 pb-6 pt-2">
<p className="mb-4 mt-2 text-xl font-semibold">
@@ -52,12 +60,21 @@ export const SecuritySection = () => {
id="isTwoFAEnabled"
isChecked={isMfaEnabled}
onCheckedChange={(state) => {
toggleMfa(state as boolean);
if (serverDetails?.emailConfigured){
toggleMfa(state as boolean);
} else {
handlePopUpOpen('setUpEmail');
}
}}
>
Enable 2-factor authentication via your personal email.
</Checkbox>
</div>
</form>
<EmailServiceSetupModal
isOpen={popUp.setUpEmail?.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle('setUpEmail', isOpen)}
/>
</>
);
};