Merge remote-tracking branch 'origin/main' into auto-capitalization-toggle

This commit is contained in:
Hasan Mansoor
2023-02-05 03:19:06 +03:00
129 changed files with 3586 additions and 1104 deletions
+28 -29
View File
@@ -187,10 +187,10 @@ const Layout = ({ children }: LayoutProps) => {
});
const userWorkspaces = orgUserProjects;
if (
userWorkspaces.length === 0 &&
router.asPath !== '/noprojects' &&
!router.asPath.includes('home') &&
!router.asPath.includes('settings') ||
(userWorkspaces.length === 0 &&
router.asPath !== '/noprojects' &&
!router.asPath.includes('home') &&
!router.asPath.includes('settings')) ||
router.asPath === '/dashboard/undefined'
) {
router.push('/noprojects');
@@ -246,16 +246,15 @@ const Layout = ({ children }: LayoutProps) => {
return (
<>
<div className="fixed w-full md:block flex flex-col h-screen">
<script src="https://cdnjs.cloudflare.com/ajax/libs/alpinejs/3.2.2/cdn.js" defer />
<div className="flex h-screen w-full flex-col overflow-x-hidden">
<NavBarDashboard />
<div className="flex flex-col md:flex-row flex-1">
<aside className="bg-bunker-600 border-r border-mineshaft-500 w-full md:w-60 h-screen">
<nav className="flex flex-col justify-between items-between h-full">
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
<aside className="w-full border-r border-mineshaft-500 bg-bunker-600 md:w-60">
<nav className="items-between flex h-full flex-col justify-between">
{/* <div className="py-6"></div> */}
<div>
<div className="flex justify-center w-full mt-[4.5rem] mb-6 bg-bunker-600 h-20 flex-col items-center px-4">
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
<div className="mt-6 mb-6 flex h-20 w-full flex-col items-center justify-center bg-bunker-600 px-4">
<div className="ml-1 mb-1 self-start text-xs font-semibold tracking-wide text-gray-400">
{t('nav:menu.project')}
</div>
{Object.keys(workspaceMapping).length > 0 ? (
@@ -279,29 +278,29 @@ const Layout = ({ children }: LayoutProps) => {
<ul>
{Object.keys(workspaceMapping).length > 0 &&
menuItems.map(({ href, title, emoji }) => (
<li className="mt-0.5 mx-2" key={title}>
<li className="mx-2 mt-0.5" key={title}>
{router.asPath.split('/')[1] === href.split('/')[1] &&
(['project', 'billing', 'org', 'personal'].includes(
router.asPath.split('/')[2]
)
? router.asPath.split('/')[2] === href.split('/')[2]
: true) ? (
<div className="flex relative px-0.5 py-2.5 text-white text-sm rounded cursor-pointer bg-primary-50/10">
<div className="absolute top-0 my-1 ml-1 inset-0 bg-primary w-1 rounded-xl mr-1" />
<p className="w-6 ml-4 mr-2 flex items-center justify-center text-lg">
<div className="relative flex cursor-pointer rounded bg-primary-50/10 px-0.5 py-2.5 text-sm text-white">
<div className="absolute inset-0 top-0 my-1 ml-1 mr-1 w-1 rounded-xl bg-primary" />
<p className="ml-4 mr-2 flex w-6 items-center justify-center text-lg">
{emoji}
</p>
{title}
</div>
) : router.asPath === '/noprojects' ? (
<div className="flex p-2.5 text-white text-sm rounded">
<p className="w-10 flex items-center justify-center text-lg">{emoji}</p>
<div className="flex rounded p-2.5 text-sm text-white">
<p className="flex w-10 items-center justify-center text-lg">{emoji}</p>
{title}
</div>
) : (
<Link href={href}>
<div className="flex p-2.5 text-white text-sm rounded cursor-pointer hover:bg-primary-50/5">
<p className="w-10 flex items-center justify-center text-lg">
<div className="flex cursor-pointer rounded p-2.5 text-sm text-white hover:bg-primary-50/5">
<p className="flex w-10 items-center justify-center text-lg">
{emoji}
</p>
{title}
@@ -312,11 +311,11 @@ const Layout = ({ children }: LayoutProps) => {
))}
</ul>
</div>
<div className="w-full mt-40 mb-4 px-2">
<div className="mt-40 mb-4 w-full px-2">
{router.asPath.split('/')[1] === 'home' ? (
<div className="flex relative px-0.5 py-2.5 text-white text-sm rounded cursor-pointer bg-primary-50/10">
<div className="absolute top-0 my-1 ml-1 inset-0 bg-primary w-1 rounded-xl mr-1" />
<p className="w-6 ml-4 mr-2 flex items-center justify-center text-lg">
<div className="relative flex cursor-pointer rounded bg-primary-50/10 px-0.5 py-2.5 text-sm text-white">
<div className="absolute inset-0 top-0 my-1 ml-1 mr-1 w-1 rounded-xl bg-primary" />
<p className="ml-4 mr-2 flex w-6 items-center justify-center text-lg">
<FontAwesomeIcon icon={faBookOpen} />
</p>
Infisical Guide
@@ -336,8 +335,8 @@ const Layout = ({ children }: LayoutProps) => {
</div>
) : (
<Link href={`/home/${workspaceMapping[workspaceSelected as any]}`}>
<div className="relative flex p-2.5 overflow-visible text-white h-10 text-sm rounded cursor-pointer bg-white/10 hover:bg-primary-50/[0.15] mt-max">
<p className="w-10 flex items-center justify-center text-lg">
<div className="mt-max relative flex h-10 cursor-pointer overflow-visible rounded bg-white/10 p-2.5 text-sm text-white hover:bg-primary-50/[0.15]">
<p className="flex w-10 items-center justify-center text-lg">
<FontAwesomeIcon icon={faBookOpen} />
</p>
Infisical Guide
@@ -369,12 +368,12 @@ const Layout = ({ children }: LayoutProps) => {
error={error}
loading={loading}
/>
<main className="flex-1 bg-bunker-800">{children}</main>
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800">{children}</main>
</div>
</div>
<div className="md:hidden bg-bunker-800 w-screen h-screen flex flex-col justify-center items-center">
<FontAwesomeIcon icon={faMobile} className="text-gray-300 text-7xl mb-8" />
<p className="text-gray-200 px-6 text-center text-lg max-w-sm">
<div className="flex h-screen w-screen flex-col items-center justify-center bg-bunker-800 md:hidden">
<FontAwesomeIcon icon={faMobile} className="mb-8 text-7xl text-gray-300" />
<p className="max-w-sm px-6 text-center text-lg text-gray-200">
{` ${t('common:no-mobile')} `}
</p>
</div>
@@ -42,15 +42,15 @@ const UpgradePlanModal = ({
leaveFrom='opacity-100 scale-100'
leaveTo='opacity-0 scale-95'
>
<Dialog.Panel className='w-full max-w-md transform overflow-hidden rounded-md bg-bunker border border-bunker-400 p-6 pt-5 text-left align-middle shadow-xl transition-all'>
<Dialog.Panel className='w-full max-w-md transform overflow-hidden rounded-md bg-bunker border border-mineshaft-500 p-6 pt-5 text-left align-middle shadow-xl transition-all'>
<Dialog.Title
as='h3'
className='text-lg font-medium leading-6 text-bunker-200'
className='text-xl font-medium leading-6 text-primary'
>
Unleash Infisical&apos;s Full Power
</Dialog.Title>
<div className='mt-2'>
<p className='text-sm text-bunker-300 mb-0.5'>
<p className='text-sm text-bunker-300 mb-1'>
{text}
</p>
<p className='text-sm text-bunker-300'>
@@ -60,7 +60,7 @@ const UpgradePlanModal = ({
<div className='mt-4'>
<button
type='button'
className='inline-flex justify-center rounded-md border border-transparent bg-primary opacity-90 hover:opacity-100 px-4 py-2 text-sm font-medium text-black hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2'
className='inline-flex justify-center rounded-md border border-transparent bg-primary opacity-80 hover:opacity-100 px-4 py-2 text-sm font-medium text-black hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2'
onClick={() => router.push(`/settings/billing/${localStorage.getItem("projectData.id")}`)}
>
Upgrade Now
@@ -41,7 +41,6 @@ const EnvironmentTable = ({ data = [], onCreateEnv, onDeleteEnv, onUpdateEnv }:
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onEnvCreateCB = async (env: Env) => {
try {
await onCreateEnv(env);
@@ -71,14 +70,14 @@ const EnvironmentTable = ({ data = [], onCreateEnv, onDeleteEnv, onUpdateEnv }:
return (
<>
<div className="flex flex-row justify-between w-full">
<div className="flex flex-col w-full">
<p className="text-xl font-semibold mb-3">Project Environments</p>
<p className="text-base text-gray-400 mb-4">
<div className="flex w-full flex-row justify-between">
<div className="flex w-full flex-col">
<p className="mb-3 text-xl font-semibold">Project Environments</p>
<p className="mb-4 text-base text-gray-400">
Choose which environments will show up in your dashboard like development, staging,
production
</p>
<p className="text-sm mr-1 text-gray-500 self-start">
<p className="mr-1 self-start text-sm text-gray-500">
Note: the text in slugs shows how these environmant should be accessed in CLI.
</p>
</div>
@@ -86,10 +85,10 @@ const EnvironmentTable = ({ data = [], onCreateEnv, onDeleteEnv, onUpdateEnv }:
<Button
text="Add New Env"
onButtonPressed={() => {
if (plan !== plans.starter || host !== "https://app.infisical.com") {
handlePopUpOpen('createUpdateEnv')
if (plan !== plans.starter || host !== 'https://app.infisical.com') {
handlePopUpOpen('createUpdateEnv');
} else {
handlePopUpOpen('upgradePlan')
handlePopUpOpen('upgradePlan');
}
}}
color="mineshaft"
@@ -98,32 +97,30 @@ const EnvironmentTable = ({ data = [], onCreateEnv, onDeleteEnv, onUpdateEnv }:
/>
</div>
</div>
<div className="table-container w-full bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
<div className="absolute rounded-t-md w-full h-12 bg-white/5" />
<table className="w-full my-1">
<div className="table-container relative mb-6 mt-1 w-full rounded-md border border-mineshaft-700 bg-bunker">
<div className="absolute h-12 w-full rounded-t-md bg-white/5" />
<table className="my-1 w-full">
<thead className="text-bunker-300">
<tr>
<th className="text-left pl-6 pt-2.5 pb-2">Name</th>
<th className="text-left pl-6 pt-2.5 pb-2">Slug</th>
<th className="pl-6 pt-2.5 pb-2 text-left">Name</th>
<th className="pl-6 pt-2.5 pb-2 text-left">Slug</th>
<th aria-label="buttons" />
</tr>
</thead>
<tbody>
{data?.length > 0 ? (
data.map(({ name, slug }) => (
<tr key={name} className="bg-bunker-800 hover:bg-bunker-800/5 duration-100">
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{name}
</td>
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">{slug}</td>
<td className="py-2 border-mineshaft-700 border-t flex justify-end">
<div className="hover:opacity-100 duration-200 flex items-center mr-2">
<tr key={name} className="bg-bunker-800 duration-100 hover:bg-bunker-800/5">
<td className="border-t border-mineshaft-700 py-2 pl-6 text-gray-300">{name}</td>
<td className="border-t border-mineshaft-700 py-2 pl-6 text-gray-300">{slug}</td>
<td className="flex justify-end border-t border-mineshaft-700 py-2">
<div className="mr-2 flex items-center duration-200 hover:opacity-100">
<Button
onButtonPressed={() => {
if (plan !== plans.starter || host !== "https://app.infisical.com") {
handlePopUpOpen('createUpdateEnv', { name, slug })
if (plan !== plans.starter || host !== 'https://app.infisical.com') {
handlePopUpOpen('createUpdateEnv', { name, slug });
} else {
handlePopUpOpen('upgradePlan')
handlePopUpOpen('upgradePlan');
}
}}
color="mineshaft"
@@ -131,13 +128,13 @@ const EnvironmentTable = ({ data = [], onCreateEnv, onDeleteEnv, onUpdateEnv }:
icon={faPencil}
/>
</div>
<div className="opacity-50 hover:opacity-100 duration-200 flex items-center mr-6">
<div className="mr-6 flex items-center opacity-50 duration-200 hover:opacity-100">
<Button
onButtonPressed={() => {
if (plan !== plans.starter || host !== "https://app.infisical.com") {
handlePopUpOpen('deleteEnv', { name, slug })
if (plan !== plans.starter || host !== 'https://app.infisical.com') {
handlePopUpOpen('deleteEnv', { name, slug });
} else {
handlePopUpOpen('upgradePlan')
handlePopUpOpen('upgradePlan');
}
}}
color="red"
@@ -150,8 +147,8 @@ const EnvironmentTable = ({ data = [], onCreateEnv, onDeleteEnv, onUpdateEnv }:
))
) : (
<tr>
<td colSpan={4} className="text-center pt-7 pb-4 text-bunker-400">
No environmants found
<td colSpan={4} className="pt-7 pb-4 text-center text-bunker-400">
No environments found
</td>
</tr>
)}
@@ -174,7 +171,7 @@ const EnvironmentTable = ({ data = [], onCreateEnv, onDeleteEnv, onUpdateEnv }:
onCreateSubmit={onEnvCreateCB}
onEditSubmit={onEnvUpdateCB}
/>
<UpgradePlanModal
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onClose={() => handlePopUpClose('upgradePlan')}
text="You can add custom environments if you switch to Infisical's Team plan."
@@ -0,0 +1,299 @@
import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import { faX } from '@fortawesome/free-solid-svg-icons';
import { plans } from 'public/data/frequentConstants';
import { useNotificationContext } from '@app/components/context/Notifications/NotificationProvider';
import { Select, SelectItem } from '@app/components/v2';
import updateUserProjectPermission from '@app/ee/api/memberships/UpdateUserProjectPermission';
import getOrganizationSubscriptions from '@app/pages/api/organization/GetOrgSubscription';
import changeUserRoleInWorkspace from '@app/pages/api/workspace/changeUserRoleInWorkspace';
import deleteUserFromWorkspace from '@app/pages/api/workspace/deleteUserFromWorkspace';
import getLatestFileKey from '@app/pages/api/workspace/getLatestFileKey';
import getProjectInfo from '@app/pages/api/workspace/getProjectInfo';
import uploadKeys from '@app/pages/api/workspace/uploadKeys';
import { decryptAssymmetric, encryptAssymmetric } from '../../utilities/cryptography/crypto';
import guidGenerator from '../../utilities/randomId';
import Button from '../buttons/Button';
import UpgradePlanModal from '../dialog/UpgradePlan';
// const roles = ['admin', 'user'];
// TODO: Set type for this
type Props = {
userData: any[];
changeData: (users: any[]) => void;
myUser: string;
filter: string;
};
type EnvironmentProps = {
name: string;
slug: string;
}
/**
* This is the component that shows the users of a certin project
* #TODO: add the possibility of choosing and doing operations on multiple users.
* @param {*} props
* @returns
*/
const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => {
const [roleSelected, setRoleSelected] = useState(
Array(userData?.length).fill(userData.map((user) => user.role))
);
const host = window.location.origin;
const router = useRouter();
const [myRole, setMyRole] = useState('member');
const [currentPlan, setCurrentPlan] = useState('');
const [workspaceEnvs, setWorkspaceEnvs] = useState<EnvironmentProps[]>([]);
const [isUpgradeModalOpen, setIsUpgradeModalOpen] = useState(false);
const { createNotification } = useNotificationContext();
const workspaceId = router.query.id as string;
// Delete the row in the table (e.g. a user)
// #TODO: Add a pop-up that warns you that the user is going to be deleted.
const handleDelete = (membershipId: string, index: number) => {
// setUserIdToBeDeleted(userId);
// onClick();
deleteUserFromWorkspace(membershipId);
changeData(userData.filter((v, i) => i !== index));
setRoleSelected([
...roleSelected.slice(0, index),
...roleSelected.slice(index + 1, userData?.length)
]);
};
// Update the rold of a certain user
const handleRoleUpdate = (index: number, e: string) => {
changeUserRoleInWorkspace(userData[index].membershipId, e.toLowerCase());
changeData([
...userData.slice(0, index),
...[
{
key: userData[index].key,
firstName: userData[index].firstName,
lastName: userData[index].lastName,
email: userData[index].email,
role: e.toLocaleLowerCase(),
status: userData[index].status,
userId: userData[index].userId,
membershipId: userData[index].membershipId,
publicKey: userData[index].publicKey,
deniedPermissions: userData[index].deniedPermissions
}
],
...userData.slice(index + 1, userData?.length)
]);
createNotification({
text: `Successfully changed user role.`,
type: 'success'
});
};
const handlePermissionUpdate = (index: number, val: string, membershipId: string, slug: string ) => {
let denials: { ability: string; environmentSlug: string; }[];
if (val === "Read Only") {
denials = [{
ability: "write",
environmentSlug: slug
}];
} else if (val === "No Access") {
denials = [{
ability: "write",
environmentSlug: slug
}, {
ability: "read",
environmentSlug: slug
}];
} else {
denials = [];
}
if (currentPlan !== plans.professional && host === 'https://app.infisical.com') {
setIsUpgradeModalOpen(true);
} else {
const allDenials = userData[index].deniedPermissions.filter((perm: { ability: string; environmentSlug: string; }) => perm.environmentSlug !== slug).concat(denials);
updateUserProjectPermission({ membershipId, denials: allDenials});
changeData([
...userData.slice(0, index),
...[
{
key: userData[index].key,
firstName: userData[index].firstName,
lastName: userData[index].lastName,
email: userData[index].email,
role: userData[index].role,
status: userData[index].status,
userId: userData[index].userId,
membershipId: userData[index].membershipId,
publicKey: userData[index].publicKey,
deniedPermissions: allDenials
}
],
...userData.slice(index + 1, userData?.length)
]);
createNotification({
text: `Successfully changed user permissions.`,
type: 'success'
});
}
};
useEffect(() => {
setMyRole(userData.filter((user) => user.email === myUser)[0]?.role);
(async () => {
const result = await getProjectInfo({ projectId: workspaceId });
setWorkspaceEnvs(result.environments);
const orgId = localStorage.getItem('orgData.id') as string;
const subscriptions = await getOrganizationSubscriptions({
orgId
});
if (subscriptions) {
setCurrentPlan(subscriptions.data[0].plan.product)
}
})();
}, [userData, myUser]);
const grantAccess = async (id: string, publicKey: string) => {
const result = await getLatestFileKey({ workspaceId });
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY') as string;
// assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({
ciphertext: result.latestKey.encryptedKey,
nonce: result.latestKey.nonce,
publicKey: result.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY
});
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: key,
publicKey,
privateKey: PRIVATE_KEY
});
uploadKeys(workspaceId, id, ciphertext, nonce);
router.reload();
};
const closeUpgradeModal = () => {
setIsUpgradeModalOpen(false);
}
return (
<div className="table-container bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1 min-w-max">
<div className="absolute rounded-t-md w-full h-[3.25rem] bg-white/5" />
<UpgradePlanModal
isOpen={isUpgradeModalOpen}
onClose={closeUpgradeModal}
text="You can change user permissions if you switch to Infisical's Professional plan."
/>
<table className="w-full my-0.5">
<thead className="text-gray-400 text-sm font-light">
<tr>
<th className="text-left pl-4 py-3.5">NAME</th>
<th className="text-left pl-4 py-3.5">EMAIL</th>
<th className="text-left pl-6 pr-10 py-3.5">ROLE</th>
{workspaceEnvs.map(env => (
<th key={guidGenerator()} className="text-left pl-8 py-1 max-w-min break-normal">
<span>{env.name.toUpperCase()}<br/></span>
{/* <span>PERMISSION</span> */}
</th>
))}
<th aria-label="buttons" />
</tr>
</thead>
<tbody>
{userData?.filter(
(user) =>
user.firstName?.toLowerCase().includes(filter) ||
user.lastName?.toLowerCase().includes(filter) ||
user.email?.toLowerCase().includes(filter)
).length > 0 &&
userData
?.filter(
(user) =>
user.firstName?.toLowerCase().includes(filter) ||
user.lastName?.toLowerCase().includes(filter) ||
user.email?.toLowerCase().includes(filter)
)
.map((row, index) => (
<tr key={guidGenerator()} className="bg-bunker-800 hover:bg-bunker-700">
<td className="pl-4 py-2 border-mineshaft-700 border-t text-gray-300">
{row.firstName} {row.lastName}
</td>
<td className="pl-4 py-2 border-mineshaft-700 border-t text-gray-300">
{row.email}
</td>
<td className="pl-6 pr-10 py-2 border-mineshaft-700 border-t text-gray-300">
<div className="justify-start h-full flex flex-row items-center">
<Select
className="w-36"
// open={isOpen}
onValueChange={(e) => handleRoleUpdate(index, e)}
value={row.role}
disabled={myRole !== 'admin' || myUser === row.email}
// onOpenChange={(open) => setIsOpen(open)}
>
<SelectItem value="admin">Admin</SelectItem>
<SelectItem value="member">Member</SelectItem>
</Select>
{row.status === 'completed' && myUser !== row.email && (
<div className="border border-mineshaft-700 rounded-md bg-white/5 hover:bg-primary text-white hover:text-black duration-200">
<Button
onButtonPressed={() => grantAccess(row.userId, row.publicKey)}
color="mineshaft"
text="Grant Access"
size="md"
/>
</div>
)}
</div>
</td>
{workspaceEnvs.map((env) => <td key={guidGenerator()} className="pl-8 py-2 border-mineshaft-700 border-t text-gray-300">
<Select
className="w-36"
// open={isOpen}
onValueChange={(val) => handlePermissionUpdate(index, val, row.membershipId, env.slug)}
value={
// eslint-disable-next-line no-nested-ternary
(row.deniedPermissions.filter((perm: any) => perm.environmentSlug === env.slug).map((perm: {ability: string}) => perm.ability).includes("write") && row.deniedPermissions.filter((perm: any) => perm.environmentSlug === env.slug).map((perm: {ability: string}) => perm.ability).includes("read"))
? "No Access"
: (row.deniedPermissions.filter((perm: any) => perm.environmentSlug === env.slug).map((perm: {ability: string}) => perm.ability).includes("write") ? "Read Only" : "Read & Write")
}
disabled={myRole !== 'admin'}
// onOpenChange={(open) => setIsOpen(open)}
>
<SelectItem value="No Access">No Access</SelectItem>
<SelectItem value="Read Only">Read Only</SelectItem>
<SelectItem value="Read & Write">Read & Write</SelectItem>
</Select>
</td>)}
<td className="flex flex-row justify-end pl-8 pr-8 py-2 border-t border-0.5 border-mineshaft-700">
{myUser !== row.email &&
// row.role !== "admin" &&
myRole !== 'member' ? (
<div className="opacity-50 hover:opacity-100 flex items-center mt-0.5">
<Button
onButtonPressed={() => handleDelete(row.membershipId, index)}
color="red"
size="icon-sm"
icon={faX}
/>
</div>
) : (
<div className="w-9 h-9" />
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
export default ProjectUsersTable;
@@ -2,8 +2,8 @@ import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import { faX } from '@fortawesome/free-solid-svg-icons';
import changeUserRoleInOrganization from '@app/pages/api/organization/changeUserRoleInOrganization';
import deleteUserFromOrganization from '@app/pages/api/organization/deleteUserFromOrganization';
import changeUserRoleInWorkspace from '@app/pages/api/workspace/changeUserRoleInWorkspace';
import deleteUserFromWorkspace from '@app/pages/api/workspace/deleteUserFromWorkspace';
import getLatestFileKey from '@app/pages/api/workspace/getLatestFileKey';
import uploadKeys from '@app/pages/api/workspace/uploadKeys';
@@ -55,9 +55,9 @@ const UserTable = ({ userData, changeData, myUser, filter, resendInvite, isOrg }
]);
};
// Update the rold of a certain user
// Update the role of a certain user
const handleRoleUpdate = (index: number, e: string) => {
changeUserRoleInWorkspace(userData[index].membershipId, e);
changeUserRoleInOrganization(String(localStorage.getItem("orgData.id")), userData[index].membershipId, e);
changeData([
...userData.slice(0, index),
...[
@@ -110,14 +110,14 @@ const UserTable = ({ userData, changeData, myUser, filter, resendInvite, isOrg }
};
return (
<div className="table-container bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
<div className="table-container bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1 min-w-max">
<div className="absolute rounded-t-md w-full h-[3.25rem] bg-white/5" />
<table className="w-full my-0.5">
<thead className="text-gray-400 text-sm font-light">
<tr>
<th className="text-left pl-6 py-3.5">FIRST NAME</th>
<th className="text-left pl-6 py-3.5">LAST NAME</th>
<th className="text-left pl-6 py-3.5">EMAIL</th>
<th className="text-left pl-4 py-3.5">NAME</th>
<th className="text-left pl-4 py-3.5">EMAIL</th>
<th className="text-left pl-6 pr-10 py-3.5">ROLE</th>
<th aria-label="buttons" />
</tr>
</thead>
@@ -136,35 +136,30 @@ const UserTable = ({ userData, changeData, myUser, filter, resendInvite, isOrg }
user.email?.toLowerCase().includes(filter)
)
.map((row, index) => (
<tr key={guidGenerator()} className="bg-bunker-800 hover:bg-bunker-800/5">
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{row.firstName}
<tr key={guidGenerator()} className="bg-bunker-800 hover:bg-bunker-700">
<td className="pl-4 py-2 border-mineshaft-700 border-t text-gray-300">
{row.firstName} {row.lastName}
</td>
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{row.lastName}
</td>
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
<td className="pl-4 py-2 border-mineshaft-700 border-t text-gray-300">
{row.email}
</td>
<td className="flex flex-row justify-end pr-8 py-2 border-t border-0.5 border-mineshaft-700">
<div className="justify-end mr-6 mx-2 w-full h-full flex flex-row items-center">
{row.status === 'granted' &&
<td className="pl-6 pr-10 py-2 border-mineshaft-700 border-t text-gray-300">
<div className="justify-start h-full flex flex-row items-center">
{row.status === 'accepted' &&
((myRole === 'admin' && row.role !== 'owner') || myRole === 'owner') &&
myUser !== row.email ? (
(myUser !== row.email) ? (
<Listbox
isSelected={row.role}
onChange={(e) => handleRoleUpdate(index, e)}
data={
myRole === 'owner' ? ['owner', 'admin', 'member'] : ['admin', 'member']
}
text="Role: "
/>
) : (
row.status !== 'invited' &&
row.status !== 'verified' && (
<Listbox
isSelected={row.role}
text="Role: "
onChange={() => {
throw new Error('Function not implemented.');
}}
@@ -173,7 +168,7 @@ const UserTable = ({ userData, changeData, myUser, filter, resendInvite, isOrg }
)
)}
{(row.status === 'invited' || row.status === 'verified') && (
<div className="w-full pl-9">
<div className="w-full pr-20">
<Button
onButtonPressed={() => deleteMembershipAndResendInvite(row.email)}
color="mineshaft"
@@ -193,10 +188,12 @@ const UserTable = ({ userData, changeData, myUser, filter, resendInvite, isOrg }
</div>
)}
</div>
</td>
<td className="flex flex-row justify-end pl-8 pr-8 py-2 border-t border-0.5 border-mineshaft-700">
{myUser !== row.email &&
// row.role !== "admin" &&
myRole !== 'member' ? (
<div className="opacity-50 hover:opacity-100 flex items-center">
<div className="opacity-50 hover:opacity-100 flex items-center mt-0.5">
<Button
onButtonPressed={() => handleDelete(row.membershipId, index)}
color="red"
@@ -0,0 +1,88 @@
import { SetStateAction, useEffect, useState } from 'react';
import Image from 'next/image';
import { WorkspaceEnv } from '@app/hooks/api/types';
import getSecretsForProject from '../utilities/secrets/getSecretsForProject';
import { Modal, ModalContent } from '../v2';
interface Secrets {
label: string;
secret: string;
}
interface CompareSecretsModalProps {
compareModal: boolean;
setCompareModal: React.Dispatch<SetStateAction<boolean>>;
selectedEnv: WorkspaceEnv;
workspaceEnvs: WorkspaceEnv[];
workspaceId: string;
currentSecret: {
key: string;
value: string;
};
}
const CompareSecretsModal = ({
compareModal,
setCompareModal,
selectedEnv,
workspaceEnvs,
workspaceId,
currentSecret
}: CompareSecretsModalProps) => {
const [secrets, setSecrets] = useState<Secrets[]>([]);
const getEnvSecrets = async () => {
const workspaceEnvironments = workspaceEnvs?.filter((env) => env !== selectedEnv);
const newSecrets = await Promise.all(
workspaceEnvironments.map(async (env) => {
// #TODO: optimize this query somehow...
const allSecrets = await getSecretsForProject({ env: env.slug, workspaceId });
const secret =
allSecrets.find((item) => item.key === currentSecret.key)?.value ?? 'Not found';
return { label: env.name, secret };
})
);
setSecrets([{ label: selectedEnv.name, secret: currentSecret.value }, ...newSecrets]);
};
useEffect(() => {
if (compareModal) {
(async () => {
await getEnvSecrets();
})();
}
}, [compareModal]);
return (
<Modal isOpen={compareModal} onOpenChange={setCompareModal}>
<ModalContent title={currentSecret?.key} onOpenAutoFocus={(e) => e.preventDefault()}>
<div className="space-y-4">
{secrets.length === 0 ? (
<div className="flex items-center bg-bunker-900 justify-center h-full py-4 rounded-md">
<Image
src="/images/loading/loading.gif"
height={60}
width={100}
alt="infisical loading indicator"
/>
</div>
) : (
secrets.map((item) => (
<div key={`${currentSecret.key}${item.label}`} className="space-y-0.5">
<p className="text-sm text-bunker-300">{item.label}</p>
<input
defaultValue={item.secret}
className="h-no-capture border border-mineshaft-500 text-md min-w-16 no-scrollbar::-webkit-scrollbar peer z-10 w-full rounded-md bg-bunker-800 px-2 py-1.5 font-mono text-gray-400 caret-white outline-none duration-200 no-scrollbar focus:ring-2 focus:ring-primary/50 "
readOnly
/>
</div>
))
)}
</div>
</ModalContent>
</Modal>
);
};
export default CompareSecretsModal;
+44 -17
View File
@@ -6,10 +6,12 @@ import { faX } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import SecretVersionList from '@app/ee/components/SecretVersionList';
import { WorkspaceEnv } from '@app/hooks/api/types';
import Button from '../basic/buttons/Button';
import Toggle from '../basic/Toggle';
import CommentField from './CommentField';
import CompareSecretsModal from './CompareSecretsModal';
import DashboardInputField from './DashboardInputField';
import { DeleteActionButton } from './DeleteActionButton';
import GenerateSecretMenu from './GenerateSecretMenu';
@@ -40,6 +42,9 @@ interface SideBarProps {
sharedToHide: string[];
setSharedToHide: (values: string[]) => void;
deleteRow: (props: DeleteRowFunctionProps) => void;
workspaceEnvs: WorkspaceEnv[];
selectedEnv: WorkspaceEnv;
workspaceId: string;
}
/**
@@ -63,15 +68,19 @@ const SideBar = ({
modifyComment,
buttonReady,
savePush,
deleteRow
deleteRow,
workspaceEnvs,
selectedEnv,
workspaceId
}: SideBarProps) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [isLoading, setIsLoading] = useState(false);
const [overrideEnabled, setOverrideEnabled] = useState(data[0].valueOverride !== undefined);
const [overrideEnabled, setOverrideEnabled] = useState(data[0]?.valueOverride !== undefined);
const [compareModal, setCompareModal] = useState(false);
const { t } = useTranslation();
return (
<div className="absolute border-l border-mineshaft-500 bg-bunker h-full w-96 top-14 right-0 z-40 shadow-xl flex flex-col justify-between">
<div className="absolute border-l border-mineshaft-500 bg-bunker h-full w-96 right-0 z-40 shadow-xl flex flex-col justify-between">
{isLoading ? (
<div className="flex items-center justify-center h-full">
<Image
@@ -171,20 +180,38 @@ const SideBar = ({
/>
</div>
)}
<div className="flex justify-start max-w-sm mt-4 px-4 mt-full mb-[4.7rem]">
<Button
text={String(t('common:save-changes'))}
onButtonPressed={savePush}
color="primary"
size="md"
active={buttonReady}
textDisabled="Saved"
/>
<DeleteActionButton
onSubmit={() =>
deleteRow({ ids: data.map((secret) => secret.id), secretName: data[0]?.key })
}
/>
<div className="mt-full mt-4 mb-4 flex max-w-sm flex-col justify-start space-y-2 px-4">
<div>
<Button
text="Compare secret across environments"
color="mineshaft"
size="md"
onButtonPressed={() => setCompareModal(true)}
/>
<CompareSecretsModal
compareModal={compareModal}
setCompareModal={setCompareModal}
currentSecret={{ key: data[0]?.key, value: data[0]?.value }}
workspaceEnvs={workspaceEnvs}
selectedEnv={selectedEnv}
workspaceId={workspaceId}
/>
</div>
<div className="flex">
<Button
text={String(t('common:save-changes'))}
onButtonPressed={savePush}
color="primary"
size="md"
active={buttonReady}
textDisabled="Saved"
/>
<DeleteActionButton
onSubmit={() =>
deleteRow({ ids: data.map((secret) => secret.id), secretName: data[0]?.key })
}
/>
</div>
</div>
</div>
);
@@ -93,7 +93,7 @@ export default function Navbar() {
};
return (
<div className="absolute flex flex-row justify-between w-full bg-bunker text-white border-b border-mineshaft-500 z-50">
<div className="flex flex-row justify-between w-full bg-bunker text-white border-b border-mineshaft-500 z-50">
<div className="m-auto flex justify-start items-center mx-4">
<div className="flex flex-row items-center">
<div className="flex justify-center py-4">
@@ -3,8 +3,8 @@ import { useRouter } from 'next/router';
import { faAngleRight } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useWorkspace } from '@app/context';
import getOrganization from '@app/pages/api/organization/GetOrg';
import getProjectInfo from '@app/pages/api/workspace/getProjectInfo';
/**
* This is the component at the top of almost every page.
@@ -23,8 +23,9 @@ export default function NavHeader({
isProjectRelated?: boolean;
}): JSX.Element {
const [orgName, setOrgName] = useState('');
const [workspaceName, setWorkspaceName] = useState('');
const router = useRouter();
const projectId = String(router.query.id);
const { currentWorkspace } = useWorkspace();
useEffect(() => {
(async () => {
@@ -33,29 +34,24 @@ export default function NavHeader({
orgId: orgId || ''
});
setOrgName(org.name);
const workspace = await getProjectInfo({
projectId: String(router.query.id)
});
setWorkspaceName(workspace.name);
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [projectId]);
return (
<div className="pt-20 ml-6 flex flex-row items-center">
<div className="bg-primary-900 h-6 w-6 rounded-md flex items-center justify-center text-mineshaft-100 mr-2">
<div className="ml-6 flex flex-row items-center pt-8">
<div className="mr-2 flex h-6 w-6 items-center justify-center rounded-md bg-primary-900 text-mineshaft-100">
{orgName?.charAt(0)}
</div>
<div className="text-primary text-sm font-semibold">{orgName}</div>
<div className="text-sm font-semibold text-primary">{orgName}</div>
{isProjectRelated && (
<>
<FontAwesomeIcon icon={faAngleRight} className="ml-3 text-sm text-gray-400 mr-3" />
<div className="font-semibold text-primary text-sm">{workspaceName}</div>
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" />
<div className="text-sm font-semibold text-primary">{currentWorkspace?.name}</div>
</>
)}
<FontAwesomeIcon icon={faAngleRight} className="ml-3 text-sm text-gray-400 mr-3" />
<div className="text-gray-400 text-sm">{pageName}</div>
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" />
<div className="text-sm text-gray-400">{pageName}</div>
</div>
);
}
@@ -4,11 +4,12 @@ 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} = process.env
export {
ENV,
POSTHOG_API_KEY,
POSTHOG_HOST,
SITE_URL,
STRIPE_PRODUCT_PRO,
STRIPE_PRODUCT_STARTER
};
STRIPE_PRODUCT_STARTER};
@@ -1,5 +1,7 @@
import { jsPDF } from 'jspdf';
import { SITE_URL } from './config';
interface PDFProps {
personalName: string;
personalEmail: string;
@@ -73,7 +75,7 @@ function generateBackupPDF({ personalName, personalEmail, generatedKey }: PDFPro
doc.roundedRect(170, 488, 375, 35, 5, 5, 'F');
doc.setTextColor(23, 23, 23);
doc.setFontSize(14);
doc.text('https://app.infisical.com/login', 180, 420);
doc.text(`${SITE_URL}/login`, 180, 420);
doc.text(personalEmail, 180, 465);
doc.text(generatedKey, 180, 510);
doc.text('Need help? Contact us at [email protected]', 32, 575);
@@ -29,8 +29,8 @@ interface SecretProps {
interface FunctionProps {
env: string;
setIsKeyAvailable: any;
setData: any;
setIsKeyAvailable?: any;
setData?: any;
workspaceId: string;
}
@@ -58,7 +58,9 @@ const getSecretsForProject = async ({
const latestKey = await getLatestFileKey({ workspaceId });
// This is called isKeyAvailable but what it really means is if a person is able to create new key pairs
setIsKeyAvailable(!latestKey ? encryptedSecrets.length === 0 : true);
if (typeof setIsKeyAvailable === 'function') {
setIsKeyAvailable(!latestKey ? encryptedSecrets.length === 0 : true);
}
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY') as string;
@@ -131,7 +133,10 @@ const getSecretsForProject = async ({
)[0]?.comment
}));
setData(result);
if (typeof setData === 'function') {
setData(result);
}
return result;
} catch (error) {
console.log('Something went wrong during accessing or decripting secrets.');
+13 -11
View File
@@ -18,14 +18,15 @@ const buttonVariants = cva(
'font-inter font-medium',
'cursor-pointer',
'inline-flex items-center justify-center',
'relative'
'relative',
'whitespace-nowrap'
],
{
variants: {
colorSchema: {
primary: ['bg-primary', 'text-black', 'border-primary hover:bg-opacity-80'],
secondary: ['bg-mineshaft', 'text-gray-300', 'border-mineshaft hover:bg-opacity-80'],
danger: ['bg-red', 'text-white', 'border-red']
danger: ['bg-red', 'text-white', 'border-red hover:bg-opacity-90']
},
variant: {
solid: '',
@@ -33,7 +34,7 @@ const buttonVariants = cva(
plain: ''
},
isDisabled: {
true: 'bg-opacity-70 cursor-not-allowed',
true: 'bg-mineshaft opacity-40 cursor-not-allowed',
false: ''
},
isFullWidth: {
@@ -45,9 +46,9 @@ const buttonVariants = cva(
false: ''
},
size: {
xs: ['text-xs', 'py-1', 'px-2'],
sm: ['text-sm', 'py-2', 'px-4'],
md: ['text-md', 'py-2', 'px-6'],
xs: ['text-xs', 'py-1', 'px-1'],
sm: ['text-sm', 'py-2', 'px-2'],
md: ['text-md', 'py-2', 'px-4'],
lg: ['text-lg', 'py-2', 'px-8']
}
},
@@ -75,7 +76,7 @@ const buttonVariants = cva(
{
colorSchema: 'secondary',
variant: 'plain',
className: 'text-mineshaft'
className: 'text-mineshaft-300'
},
{
colorSchema: 'danger',
@@ -101,7 +102,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
children,
isDisabled = false,
className = '',
size = 'md',
size = 'sm',
variant = 'solid',
isFullWidth,
isRounded = true,
@@ -144,9 +145,10 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
)}
<span
className={twMerge(
'transition-all shrink-0 cursor-pointer',
'shrink-0 cursor-pointer transition-all',
loadingToggleClass,
size === 'xs' ? 'mr-1' : 'mr-2'
leftIcon && 'ml-2',
size === 'xs' ? 'mr-1' : 'mr-3'
)}
>
{leftIcon}
@@ -154,7 +156,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
<span className={twMerge('transition-all', loadingToggleClass)}>{children}</span>
<span
className={twMerge(
'transition-all shrink-0 cursor-pointer',
'shrink-0 cursor-pointer transition-all',
loadingToggleClass,
size === 'xs' ? 'ml-1' : 'ml-2'
)}
+3 -3
View File
@@ -8,7 +8,7 @@ export type CardTitleProps = {
};
export const CardTitle = ({ children, className, subTitle }: CardTitleProps) => (
<div className={twMerge('p-6 pb-4 font-sans text-xl font-medium', className)}>
<div className={twMerge('px-6 py-4 font-sans text-xl font-medium', className)}>
{children}
{subTitle && <p className="py-1 text-sm font-normal text-gray-400">{subTitle}</p>}
</div>
@@ -29,7 +29,7 @@ export type CardBodyProps = {
};
export const CardBody = ({ children, className }: CardBodyProps) => (
<div className={twMerge('p-6 pt-0', className)}>{children}</div>
<div className={twMerge('px-6 pb-6 pt-0', className)}>{children}</div>
);
export type CardProps = {
@@ -47,7 +47,7 @@ export const Card = forwardRef<HTMLDivElement, CardProps>(
<div
ref={ref}
className={twMerge(
'flex flex-col w-full font-inter text-gray-200 bg-mineshaft-700 shadow-md',
'flex w-full flex-col bg-mineshaft-800 font-inter text-gray-200 shadow-md',
isFullHeight && 'h-full',
isRounded && 'rounded-md',
isPlain && 'shadow-none',
@@ -0,0 +1,89 @@
import { useEffect, useState } from 'react';
import { useToggle } from '@app/hooks';
import { Button } from '../Button';
import { FormControl } from '../FormControl';
import { Input } from '../Input';
import { Modal, ModalContent } from '../Modal';
type Props = {
isOpen?: boolean;
onClose?: () => void;
onChange?: (isOpen: boolean) => void;
deleteKey: string;
title: string;
onDeleteApproved: () => Promise<void>;
};
export const DeleteActionModal = ({
isOpen,
onClose,
onChange,
deleteKey,
onDeleteApproved,
title
}: Props): JSX.Element => {
const [inputData, setInputData] = useState('');
const [isLoading, setIsLoading] = useToggle();
useEffect(() => {
setInputData('');
}, [isOpen]);
const onDelete = async () => {
setIsLoading.on();
try {
await onDeleteApproved();
} catch {
setIsLoading.off();
} finally {
setIsLoading.off();
}
};
return (
<Modal
isOpen={isOpen}
onOpenChange={(isOpenState) => {
setInputData('');
if (onChange) onChange(isOpenState);
}}
>
<ModalContent
title={title}
subTitle="This action is irreversible!!!"
footerContent={
<div className="flex items-center">
<Button
className="mr-4"
colorSchema="danger"
isDisabled={!(deleteKey === inputData) || isLoading}
onClick={onDelete}
isLoading={isLoading}
>
Delete
</Button>
<Button variant="plain" colorSchema="secondary" onClick={onClose}>
Cancel
</Button>
</div>
}
onClose={onClose}
>
<form>
<FormControl
label={
<div className="pb-2 text-sm">
Type <span className="font-bold">{deleteKey}</span> to delete the resource
</div>
}
className="mb-4"
>
<Input value={inputData} onChange={(e) => setInputData(e.target.value)} />
</FormControl>
</form>
</ModalContent>
</Modal>
);
};
@@ -0,0 +1 @@
export { DeleteActionModal } from './DeleteActionModal';
@@ -11,7 +11,7 @@ export type FormLabelProps = {
};
export const FormLabel = ({ id, label, isRequired }: FormLabelProps) => (
<Label.Root className="text-mineshaft-300 text-sm font-medium block mb-1 ml-0.5" htmlFor={id}>
<Label.Root className="mb-1 ml-0.5 block text-sm font-medium text-mineshaft-300" htmlFor={id}>
{label}
{isRequired && <span className="ml-1 text-red">*</span>}
</Label.Root>
@@ -25,7 +25,7 @@ export type FormHelperTextProps = {
export const FormHelperText = ({ isError, text }: FormHelperTextProps) => (
<div
className={twMerge(
'text-xs font-inter flex items-center opacity-90 text-mineshaft-300 mt-2',
'mt-2 flex items-center font-inter text-xs text-mineshaft-300 opacity-90',
isError && 'text-red-600'
)}
>
@@ -46,6 +46,7 @@ export type FormControlProps = {
helperText?: ReactNode;
errorText?: ReactNode;
children: JSX.Element;
className?: string;
};
export const FormControl = ({
@@ -55,10 +56,11 @@ export const FormControl = ({
helperText,
errorText,
id,
isError
isError,
className
}: FormControlProps): JSX.Element => {
return (
<div>
<div className={twMerge('mb-4', className)}>
{typeof label === 'string' ? (
<FormLabel label={label} isRequired={isRequired} id={id} />
) : (
@@ -14,7 +14,7 @@ const iconButtonVariants = cva(
[
'button',
'transition-all',
'font-inter font-medium',
'font-inter font-medium user-select-none',
'cursor-pointer',
'inline-flex items-center justify-center',
'relative'
@@ -22,9 +22,9 @@ const iconButtonVariants = cva(
{
variants: {
colorSchema: {
primary: ['bg-primary', 'text-black', 'border-primary hover:bg-opacity-80'],
secondary: ['bg-mineshaft', 'text-gray-300', 'border-mineshaft hover:bg-opacity-80'],
danger: ['bg-red', 'text-white', 'border-red']
primary: ['bg-primary', 'text-black', 'border-primary hover:opacity-80'],
secondary: ['bg-mineshaft', 'text-gray-300', 'border-mineshaft hover:bg-bunker-400'],
danger: ['bg-[#973939] hover:bg-red', 'text-white', 'border-red']
},
variant: {
solid: '',
@@ -40,10 +40,10 @@ const iconButtonVariants = cva(
false: ''
},
size: {
xs: ['text-xs', 'py-1', 'px-2'],
sm: ['text-sm', 'py-2', 'px-3'],
md: ['text-md', 'py-3', 'px-4'],
lg: ['text-lg', 'py-3', 'px-6']
xs: ['text-xs', 'py-2', 'px-2'],
sm: ['text-sm', 'py-3', 'px-3'],
md: ['text-md', 'py-4', 'px-4'],
lg: ['text-lg', 'py-6', 'px-6']
}
},
compoundVariants: [
@@ -97,7 +97,7 @@ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
ariaLabel,
isDisabled = false,
className,
size = 'md',
size = 'sm',
variant = 'solid',
isRounded = true,
colorSchema = 'primary',
+7 -4
View File
@@ -8,10 +8,11 @@ type Props = {
isRequired?: boolean;
leftIcon?: ReactNode;
rightIcon?: ReactNode;
isDisabled?: boolean;
};
const inputVariants = cva(
'input w-full py-2 text-gray-400 placeholder-gray-500 placeholder-opacity-50',
'input w-full py-2 text-gray-400 placeholder-gray-500 placeholder-opacity-50 outline-none focus:ring-2',
{
variants: {
size: {
@@ -46,7 +47,7 @@ const inputParentContainerVariants = cva('inline-flex font-inter items-center bo
},
isError: {
true: 'border-red',
false: 'border-mineshaft-400'
false: 'border-mineshaft-500'
},
isFullWidth: {
true: 'w-full',
@@ -70,6 +71,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
className,
isRounded = true,
isFullWidth = true,
isDisabled,
isError = false,
isRequired,
leftIcon,
@@ -87,9 +89,10 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
{...props}
required={isRequired}
ref={ref}
disabled={isDisabled}
className={twMerge(
leftIcon ? 'pl-9' : 'pl-4',
rightIcon ? 'pr-9' : 'pr-4',
leftIcon ? 'pl-9' : 'pl-2.5',
rightIcon ? 'pr-9' : 'pr-2.5',
inputVariants({ className, isError, size, isRounded, variant })
)}
/>
+11 -7
View File
@@ -11,33 +11,34 @@ export type ModalContentProps = DialogPrimitive.DialogContentProps & {
title?: ReactNode;
subTitle?: string;
footerContent?: ReactNode;
onClose?: () => void;
};
export const ModalContent = forwardRef<HTMLDivElement, ModalContentProps>(
({ children, title, subTitle, className, footerContent, ...props }, forwardedRef) => (
({ children, title, subTitle, className, footerContent, onClose, ...props }, forwardedRef) => (
<DialogPrimitive.Portal>
<DialogPrimitive.Overlay
className="fixed inset-0 w-full h-full animate-fadeIn"
style={{ backgroundColor: 'rgba(0, 0, 0, 0.5)' }}
className="fixed inset-0 h-full w-full animate-fadeIn"
style={{ backgroundColor: 'rgba(0, 0, 0, 0.7)' }}
/>
<DialogPrimitive.Content {...props} ref={forwardedRef}>
<Card
isRounded
className={twMerge(
'fixed max-w-md animate-popIn top-1/2 left-1/2 -translate-y-2/4 -translate-x-2/4',
'fixed top-1/2 left-1/2 max-w-lg -translate-y-2/4 -translate-x-2/4 animate-popIn drop-shadow-2xl z-50',
className
)}
>
{title && <CardTitle subTitle={subTitle}>{title}</CardTitle>}
<CardBody>{children}</CardBody>
{footerContent && <CardFooter>{footerContent}</CardFooter>}
<DialogPrimitive.Close aria-label="Close" asChild>
<DialogPrimitive.Close aria-label="Close" asChild onClick={onClose}>
<IconButton
variant="plain"
ariaLabel="close"
className="absolute top-2.5 right-2.5 text-white hover:bg-gray-600 rounded"
className="absolute top-3 right-3 rounded text-white hover:bg-gray-600"
>
<FontAwesomeIcon icon={faTimes} size="sm" className="cursor-pointer" />
<FontAwesomeIcon icon={faTimes} size="lg" className="cursor-pointer" />
</IconButton>
</DialogPrimitive.Close>
</Card>
@@ -55,3 +56,6 @@ export const Modal = ({ isOpen, ...props }: ModalProps) => (
export const ModalTrigger = DialogPrimitive.Trigger;
export type ModalTriggerProps = DialogPrimitive.DialogTriggerProps;
export const ModalClose = DialogPrimitive.Close;
export type ModalCloseProps = DialogPrimitive.DialogCloseProps;
+2 -2
View File
@@ -1,2 +1,2 @@
export type { ModalContentProps, ModalProps, ModalTriggerProps } from './Modal';
export { Modal, ModalContent, ModalTrigger } from './Modal';
export type { ModalCloseProps, ModalContentProps, ModalProps, ModalTriggerProps } from './Modal';
export { Modal, ModalClose, ModalContent, ModalTrigger } from './Modal';
+23 -17
View File
@@ -10,39 +10,45 @@ type Props = {
children: ReactNode;
placeholder?: string;
className?: string;
dropdownContainerClassName?: string;
isLoading?: boolean;
};
export type SelectProps = SelectPrimitive.SelectProps & Props;
export const Select = forwardRef<HTMLButtonElement, SelectProps>(
({ children, placeholder, className, isLoading, ...props }, ref): JSX.Element => {
(
{ children, placeholder, className, isLoading, dropdownContainerClassName, ...props },
ref
): JSX.Element => {
return (
<SelectPrimitive.Root {...props}>
<SelectPrimitive.Trigger
ref={ref}
className={twMerge(
`inline-flex items-center justify-between data-[placeholder]:text-gray-500
px-4 py-2.5 font-inter text-sm text-white rounded-md bg-mineshaft-800`,
`inline-flex items-center justify-between rounded-md
bg-bunker-800 px-3 py-2 font-inter text-sm font-normal text-bunker-200 outline-none data-[placeholder]:text-gray-500`,
className
)}
>
<SelectPrimitive.Value placeholder={placeholder} />
<SelectPrimitive.Icon className="ml-3">
<FontAwesomeIcon icon={faChevronDown} size="sm" />
</SelectPrimitive.Icon>
{!props.disabled && (
<SelectPrimitive.Icon className="ml-3">
<FontAwesomeIcon icon={faChevronDown} size="sm" />
</SelectPrimitive.Icon>
)}
</SelectPrimitive.Trigger>
<SelectPrimitive.Portal>
<SelectPrimitive.Content
position="popper"
sideOffset={5}
className="overflow-hidden text-white rounded-md shadow-md font-inter bg-mineshaft-800"
style={{ width: 'var(--radix-select-trigger-width)' }}
className={twMerge(
'relative left-4 top-1 overflow-hidden rounded-md bg-bunker-800 font-inter text-bunker-100 shadow-md',
dropdownContainerClassName
)}
>
<SelectPrimitive.ScrollUpButton>
<FontAwesomeIcon icon={faChevronUp} size="sm" />
</SelectPrimitive.ScrollUpButton>
<SelectPrimitive.Viewport className="p-2">
<SelectPrimitive.Viewport className="p-1.5">
{isLoading ? (
<div className="flex items-center justify-center">
<Spinner size="xs" />
@@ -75,16 +81,16 @@ export const SelectItem = forwardRef<HTMLDivElement, SelectItemProps>(
<SelectPrimitive.Item
{...props}
className={twMerge(
`text-sm rounded-sm transition-all hover:text-primary
hover:bg-mineshaft-700 flex items-center pl-10 pr-4 py-2 cursor-pointer
select-none outline-none relative`,
isSelected && 'text-primary',
isDisabled && 'text-gray-600 hover:bg-transparent cursor-not-allowed hover:text-gray-600',
`relative flex cursor-pointer
select-none items-center rounded-md py-2 pl-10 pr-4 text-sm
outline-none transition-all hover:bg-mineshaft-500`,
isSelected && 'bg-primary',
isDisabled && 'cursor-not-allowed text-gray-600 hover:bg-transparent hover:text-gray-600',
className
)}
ref={forwardedRef}
>
<SelectPrimitive.ItemIndicator className="absolute left-2">
<SelectPrimitive.ItemIndicator className="absolute left-3.5">
<FontAwesomeIcon icon={faCheck} size="sm" />
</SelectPrimitive.ItemIndicator>
<SelectPrimitive.ItemText className="">{children}</SelectPrimitive.ItemText>
+17 -13
View File
@@ -1,4 +1,4 @@
import { ReactNode } from 'react';
import { HTMLAttributes, ReactNode, TdHTMLAttributes } from 'react';
import { twMerge } from 'tailwind-merge';
export type TableContainerProps = {
@@ -14,7 +14,7 @@ export const TableContainer = ({
}: TableContainerProps): JSX.Element => (
<div
className={twMerge(
'overflow-x-auto font-inter shadow-md relative border border-solid border-mineshaft-700',
'relative w-full overflow-x-auto border border-solid border-mineshaft-700 font-inter shadow-md',
isRounded && 'rounded-md',
className
)}
@@ -32,7 +32,7 @@ export type TableProps = {
export const Table = ({ children, className }: TableProps): JSX.Element => (
<table
className={twMerge(
'bg-bunker-800 p-2 roun text-gray-300 w-full text-sm text-left rounded-md',
'w-full rounded rounded-md bg-bunker-800 p-2 text-left text-sm text-gray-300',
className
)}
>
@@ -47,7 +47,7 @@ export type THeadProps = {
};
export const THead = ({ children, className }: THeadProps): JSX.Element => (
<thead className={twMerge('text-xs bg-bunker text-bunker-300 uppercase', className)}>
<thead className={twMerge('bg-bunker text-xs uppercase text-bunker-300', className)}>
{children}
</thead>
);
@@ -56,20 +56,22 @@ export const THead = ({ children, className }: THeadProps): JSX.Element => (
export type TrProps = {
children: ReactNode;
className?: string;
};
} & HTMLAttributes<HTMLTableRowElement>;
export const Tr = ({ children, className }: TrProps): JSX.Element => (
<tr className={twMerge('border border-solid border-mineshaft-700', className)}>{children}</tr>
export const Tr = ({ children, className, ...props }: TrProps): JSX.Element => (
<tr className={twMerge('border border-solid border-mineshaft-700 hover:bg-bunker-700', className)} {...props}>
{children}
</tr>
);
// table head columns
export type ThProps = {
children: ReactNode;
children?: ReactNode;
className?: string;
};
export const Th = ({ children, className }: ThProps): JSX.Element => (
<th className={twMerge('px-6 py-3 font-medium', className)}>{children}</th>
<th className={twMerge('px-5 pt-4 pb-3.5 font-medium font-semibold bg-bunker-500', className)}>{children}</th>
);
// table body
@@ -84,10 +86,12 @@ export const TBody = ({ children, className }: TBodyProps): JSX.Element => (
// table body columns
export type TdProps = {
children: ReactNode;
children?: ReactNode;
className?: string;
};
} & TdHTMLAttributes<HTMLTableCellElement>;
export const Td = ({ children, className }: TdProps): JSX.Element => (
<td className={twMerge('text-left px-6 py-3', className)}>{children}</td>
export const Td = ({ children, className, ...props }: TdProps): JSX.Element => (
<td className={twMerge('px-5 py-2 text-left', className)} {...props}>
{children}
</td>
);
@@ -0,0 +1,36 @@
import Link from 'next/link';
import { Button } from '../Button';
import { Modal, ModalClose, ModalContent } from '../Modal';
type Props = {
isOpen?: boolean;
onOpenChange?: (isOpen: boolean) => void;
text: string;
};
export const UpgradePlanModal = ({ text, isOpen, onOpenChange }: Props): JSX.Element => (
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent
title="Unleash Infisical's Full Power"
footerContent={[
<Link
href={`/settings/billing/${localStorage.getItem('projectData.id') as string}`}
key="upgrade-plan"
>
<Button className="mr-4">Upgrade Plan</Button>
</Link>,
<ModalClose asChild key="upgrade-plan-cancel">
<Button colorSchema="secondary" variant="plain">
Cancel
</Button>
</ModalClose>
]}
>
<p className="mb-4 text-bunker-300">{text}</p>
<p className="font-medium text-bunker-300">
Upgrade and get access to this, as well as to other powerful enhancements.
</p>
</ModalContent>
</Modal>
);
@@ -0,0 +1 @@
export { UpgradePlanModal } from './UpgradePlanModal';
+2
View File
@@ -1,6 +1,7 @@
export * from './Button';
export * from './Card';
export * from './Checkbox';
export * from './DeleteActionModal';
export * from './Dropdown';
export * from './FormControl';
export * from './IconButton';
@@ -12,3 +13,4 @@ export * from './Spinner';
export * from './Switch';
export * from './Table';
export * from './TextArea';
export * from './UpgradePlanModal';