Compare commits

...
Author SHA1 Message Date
dboreham af284953f3 Add newline to trigger re-build in CD 2024-07-04 15:44:12 -06:00
Vivian Phung 9f0a2ad548 space 2024-07-03 12:41:39 -04:00
Vivian Phung bd10e2cb35 fix: version temp 2024-06-25 01:34:21 -04:00
Vivian Phung eb32385cf3 revert fix 2024-06-25 01:14:08 -04:00
Vivian Phung aebb20b987 update README.md debugging deployer 2024-06-25 01:08:38 -04:00
Vivian Phung 8a2b51952f fix: temp frontend (#230)
* v.0.0.9 staging

* fix: temp frontend deployment bug
2024-06-25 01:04:03 -04:00
Vivian Phung 94f46f9621 v.0.0.9 staging (#229) 2024-06-24 19:52:51 -04:00
Vivian Phung e751addcce Update local.toml.example in packages/backend (#228)
### TL;DR

Added new keys for Google and Turnkey integration in `local.toml.example`

### What changed?

- Updated server session secret to empty string
- Added placeholders for google clientId and clientSecret
- Added placeholders for Turnkey API integration (apiBaseUrl, apiPrivateKey, apiPublicKey, defaultOrganizationId)

### How to test?

1. Pull the latest changes
2. Update `local.toml.example` file with actual values.
3. Run the application locally and ensure all services are working correctly.

### Why make this change?

To integrate new services (Google and Turnkey) into the project setup.

---
2024-06-24 19:42:33 -04:00
Vivian Phung c01f8fdabf Refactor: components/shared (#227)
### TL;DR

This pull request consolidates all shared component exports into a new `index.ts` file within the `components/shared` directory. The purpose is to streamline and centralize the exports of shared components for improved maintainability.

### What changed?

- Created a new `index.ts` file in the `components/shared` directory that exports all shared components.
- Updated import statements to use the new centralized `index.ts` file instead of individual component files.

### How to test?

1. Run the frontend application.
2. Navigate through the application to ensure all shared components render correctly without console errors.

### Why make this change?

The motivation behind this change is to organize exports of shared components in a single file. This enhances code maintainability and simplifies the process of importing shared components across the project.

---
2024-06-24 19:36:08 -04:00
Vivian Phung 2b60114dab refactor: use onToast remove react-hot-toast dep (#226)
### TL;DR

Removed the dependency on `react-hot-toast` in favor of a custom implementation for toast notifications.

### What changed?

1. Removed `react-hot-toast` from `package.json` and `yarn.lock`. 
2. Updated `EnvironmentVariables` and `Config` to use the custom toast notification system.

### Why make this change?

To reduce bundle size and have more control over the toast notification system.

### How to test?

1. Navigate to the Environment Variables settings for a project. Try adding and removing environment variables to see the new toast notifications in action.
2. Navigate to the Domains settings for a project and try adding a new domain to view the custom toast notifications.

---
2024-06-24 19:25:31 -04:00
Vivian Phung 9a1c0e8338 feat(domains): DomainCard and WebhookCard styling start (#225)
### TL;DR

Refactored the `DomainCard`, `EditDomainDialog`, and `WebhookCard` components to improve code readability and enhance UI using new shared components like `Tag`, `Heading`, `Button`, and `CustomIcon`.

### What changed?

- `DomainCard` component:
  - Replaced `Chip` with `Tag` component.
  - Used `Heading`, `Button`, and `CustomIcon` components.
  - Updated refresh icon to show `LoadingIcon` when checking.
- `EditDomainDialog` component:
  - Used `useToast` hook for toast messages.
- `WebhookCard` component:
  - Used `Input`, `Button`, and `CustomIcon` components for better UI.
- Added Storybook stories for the updated components.

### How to test?

1. Go to the project settings page.
2. Verify the `DomainCard` UI updates.
3. Edit a domain and check the toasts.
4. Verify the `WebhookCard` UI and functionality.
5. Run Storybook and inspect the added stories for the components.

### Why make this change?

To improve the consistency and user experience of the project settings UI, and to make the components more maintainable by using shared components.

---
2024-06-24 19:22:20 -04:00
Vivian Phung 1b038476c7 feat: Settings screen for org (#224)
This update introduces a new header section within the `Settings` page. It incorporates a `Heading` component from the shared components library, ensuring consistent styling and structure across the app. The `Settings` page layout now features appropriate padding and flexbox for better alignment and spacing.

---
2024-06-24 18:57:38 -04:00
Vivian Phung 4a78eb13f6 fix: ProjectSearchBarDialog (search small screen) Suggestions once (#223)
Refactor the rendering logic of the suggestion list in `ProjectSearchBarDialog` component. This change simplifies the conditional rendering by restructuring the JSX to be more readable and maintainable. Now, the 'Suggestions' label is rendered once if there are items, and the items are mapped afterward.

---
2024-06-24 18:54:32 -04:00
Vivian Phung 41bcb2e7d0 fix: ProjectSearchBarDialog suppressRefError (#222)
This PR resolves issues with the Project Search Bar component where `getMenuProps` was causing reference errors. By adding `suppressRefError: true` to `getMenuProps` in both `ProjectSearchBar` and `ProjectSearchBarDialog`, the warnings are suppressed.

---
2024-06-24 18:51:31 -04:00
Vivian Phung f981f1a3f6 fix(ProjectSearchBarDialog): getMenuProps error (#221)
- Replaced `useDebounce` with `useDebounceValue` for better type inference and simplicity
- Added `getMenuProps` to `useCombobox` to support better accessibility and usability
- Minor style tweak to improve `ProjectSearch` header hover effect
- Created Storybook stories for the `ProjectSearchBar` component

---
2024-06-24 18:48:32 -04:00
Vivian Phung dee84f18cb feat: dynamic project success page and update links (#220)
### TL;DR

Integrates project data fetching for dynamic subdomain display on the Project Deployment Success page and the OverviewTabPanel.

### What changed?

- Updated `Id.tsx` to fetch project data and dynamically display project's subdomain after deployment.
- Modified `Overview.tsx` to make project's subdomain a clickable link.

### How to test?

1. Deploy a new project and check the deployment success page for correct subdomain display.
2. Open a project's overview tab and click the subdomain link to ensure it navigates correctly.

### Why make this change?

Improves user experience by displaying the actual subdomain and making it clickable, ensuring users can conveniently verify their deployment and access project domain.

---
2024-06-24 18:44:34 -04:00
Vivian Phung 44015d5451 feat(ui): loading spinner on project creation (#219)
### TL;DR
This pull request adds a loading icon to the 'Deploy' button in the project template creation form.

### What changed?
- Imported `LoadingIcon` from `components/shared/CustomIcon`.
- Modified the `Button` component to conditionally display the `LoadingIcon` when `isLoading` is `true`. The icon will animate by spinning.

### How to test?
1. Navigate to the project template creation form.
2. Fill in the required fields.
3. Click the 'Deploy' button.
4. Ensure the loading icon appears and spins when the button is disabled (when `isLoading` is `true`).

### Why make this change?
This change provides visual feedback to users, indicating that their action is being processed, thereby enhancing user experience.

---
2024-06-24 18:41:31 -04:00
Vivian Phung a684743bd6 feat(template projects): generate git repo on backend (#218)
### TL;DR

- Still cretaes app if user migrates from page

The PR introduces a new `AddProjectFromTemplate` mutation to facilitate project creation using a repository template. This change centralizes the template project creation logic within the backend, improving code maintainability by removing redundant client-side code.

### What changed?
- Added `AddProjectFromTemplate` input type in `schema.gql` and corresponding TypeScript interfaces.
- Implemented `addProjectFromTemplate` resolver with error handling and Octokit integration for repository creation.
- Updated `service.ts` to include the new `addProjectFromTemplate` method.
- Created new GraphQL `Mutation` for `addProjectFromTemplate` in the GraphQL schema.
- Adjusted the client-side GQLClient to support the new mutation.
- Modified frontend to utilize the new backend mutation for project creation from a template.

### How to test?
1. Ensure your backend server is running.
2. Use a GraphQL client like Postman to call the `addProjectFromTemplate` mutation with appropriate input.
3. Verify that the new project is created using the specified template, and appropriate error messages are returned for failures.
4. Check the frontend flow for creating a project from a template to ensure it is working correctly.

### Why make this change?
This change enhances code maintainability by centralizing template project creation logic within the backend, thereby reducing redundancy and potential inconsistencies in client-side implementations.

---
2024-06-24 18:38:01 -04:00
42 changed files with 1099 additions and 577 deletions
+1
View File
@@ -21,3 +21,4 @@ yarn build --ignore frontend
### Environment variables, running the development server, and deployment ### Environment variables, running the development server, and deployment
Follow the instructions in the README.md files of the [backend](packages/backend/README.md) and [frontend](packages/frontend/README.md) packages. Follow the instructions in the README.md files of the [backend](packages/backend/README.md) and [frontend](packages/frontend/README.md) packages.
+1 -1
View File
@@ -44,7 +44,7 @@ yarn start
## Deployment ## Deployment
Clone the deployer repository: Clone the [deployer repository](https://git.vdb.to/cerc-io/snowballtools-base-api-deployments):
```zsh ```zsh
git clone git@git.vdb.to:cerc-io/snowballtools-base-api-deployments.git git clone git@git.vdb.to:cerc-io/snowballtools-base-api-deployments.git
@@ -3,7 +3,7 @@
port = 8000 port = 8000
gqlPath = "/graphql" gqlPath = "/graphql"
[server.session] [server.session]
secret = "p4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi" secret = ""
appOriginUrl = "http://localhost:3000" appOriginUrl = "http://localhost:3000"
trustProxy = false trustProxy = false
domain = "localhost" domain = "localhost"
@@ -17,6 +17,16 @@
clientId = "" clientId = ""
clientSecret = "" clientSecret = ""
[google]
clientId = ""
clientSecret = ""
[turnkey]
apiBaseUrl = "https://api.turnkey.com"
apiPrivateKey = ""
apiPublicKey = ""
defaultOrganizationId = ""
[registryConfig] [registryConfig]
fetchDeploymentRecordDelay = 5000 fetchDeploymentRecordDelay = 5000
restEndpoint = "http://localhost:1317" restEndpoint = "http://localhost:1317"
+21
View File
@@ -6,6 +6,7 @@ import { Permission } from './entity/ProjectMember';
import { Domain } from './entity/Domain'; import { Domain } from './entity/Domain';
import { Project } from './entity/Project'; import { Project } from './entity/Project';
import { EnvironmentVariable } from './entity/EnvironmentVariable'; import { EnvironmentVariable } from './entity/EnvironmentVariable';
import { AddProjectFromTemplateInput } from './types';
const log = debug('snowball:resolver'); const log = debug('snowball:resolver');
@@ -197,6 +198,26 @@ export const createResolvers = async (service: Service): Promise<any> => {
} }
}, },
addProjectFromTemplate: async (
_: any,
{
organizationSlug,
data,
}: { organizationSlug: string; data: AddProjectFromTemplateInput },
context: any,
) => {
try {
return await service.addProjectFromTemplate(
context.user,
organizationSlug,
data,
);
} catch (err) {
log(err);
throw err;
}
},
addProject: async ( addProject: async (
_: any, _: any,
{ {
+1 -1
View File
@@ -3,7 +3,7 @@ import { Router } from 'express';
const router = Router(); const router = Router();
router.get('/version', async (req, res) => { router.get('/version', async (req, res) => {
return res.send({ version: '0.0.8' }); return res.send({ version: '0.0.9' });
}); });
export default router; export default router;
+12
View File
@@ -130,6 +130,14 @@ input AddEnvironmentVariableInput {
value: String! value: String!
} }
input AddProjectFromTemplateInput {
templateOwner: String!
templateRepo: String!
owner: String!
name: String!
isPrivate: Boolean!
}
input AddProjectInput { input AddProjectInput {
name: String! name: String!
repository: String! repository: String!
@@ -204,6 +212,10 @@ type Mutation {
): Boolean! ): Boolean!
removeEnvironmentVariable(environmentVariableId: String!): Boolean! removeEnvironmentVariable(environmentVariableId: String!): Boolean!
updateDeploymentToProd(deploymentId: String!): Boolean! updateDeploymentToProd(deploymentId: String!): Boolean!
addProjectFromTemplate(
organizationSlug: String!
data: AddProjectFromTemplateInput
): Project!
addProject(organizationSlug: String!, data: AddProjectInput): Project! addProject(organizationSlug: String!, data: AddProjectInput): Project!
updateProject(projectId: String!, data: UpdateProjectInput): Boolean! updateProject(projectId: String!, data: UpdateProjectInput): Boolean!
redeployToProd(deploymentId: String!): Boolean! redeployToProd(deploymentId: String!): Boolean!
+51 -1
View File
@@ -16,6 +16,7 @@ import { User } from './entity/User';
import { Registry } from './registry'; import { Registry } from './registry';
import { GitHubConfig, RegistryConfig } from './config'; import { GitHubConfig, RegistryConfig } from './config';
import { import {
AddProjectFromTemplateInput,
AppDeploymentRecord, AppDeploymentRecord,
AppDeploymentRemovalRecord, AppDeploymentRemovalRecord,
GitPushEventPayload, GitPushEventPayload,
@@ -643,6 +644,53 @@ export class Service {
return newDeployment; return newDeployment;
} }
async addProjectFromTemplate(
user: User,
organizationSlug: string,
data: AddProjectFromTemplateInput,
): Promise<Project | undefined> {
try {
const octokit = await this.getOctokit(user.id);
const gitRepo = await octokit?.rest.repos.createUsingTemplate({
template_owner: data.templateOwner,
template_repo: data.templateRepo,
owner: data.owner,
name: data.name,
include_all_branches: false,
private: data.isPrivate,
});
if (!gitRepo) {
throw new Error('Failed to create repository from template');
}
const createdTemplateRepo = await octokit.rest.repos.get({
owner: data.owner,
repo: data.name,
});
const prodBranch = createdTemplateRepo.data.default_branch ?? 'main';
const project = await this.addProject(user, organizationSlug, {
name: `${gitRepo.data.owner!.login}-${gitRepo.data.name}`,
prodBranch,
repository: gitRepo.data.full_name,
// TODO: Set selected template
template: 'webapp',
});
if (!project || !project.id) {
throw new Error('Failed to create project from template');
}
return project;
} catch (error) {
console.error('Error creating project from template:', error);
throw error;
}
}
async addProject( async addProject(
user: User, user: User,
organizationSlug: string, organizationSlug: string,
@@ -672,7 +720,7 @@ export class Service {
}); });
// Create deployment with prod branch and latest commit // Create deployment with prod branch and latest commit
await this.createDeployment(user.id, octokit, { const deployment = await this.createDeployment(user.id, octokit, {
project, project,
branch: project.prodBranch, branch: project.prodBranch,
environment: Environment.Production, environment: Environment.Production,
@@ -683,6 +731,8 @@ export class Service {
await this.createRepoHook(octokit, project); await this.createRepoHook(octokit, project);
console.log('projectid is', project.id);
return project; return project;
} }
+9 -1
View File
@@ -41,7 +41,7 @@ export interface AppDeploymentRecordAttributes {
export interface AppDeploymentRemovalRecordAttributes { export interface AppDeploymentRemovalRecordAttributes {
deployment: string; deployment: string;
request: string; request: string;
type: "ApplicationDeploymentRemovalRecord"; type: 'ApplicationDeploymentRemovalRecord';
version: string; version: string;
} }
@@ -61,3 +61,11 @@ export interface AppDeploymentRecord extends RegistryRecord {
export interface AppDeploymentRemovalRecord extends RegistryRecord { export interface AppDeploymentRemovalRecord extends RegistryRecord {
attributes: AppDeploymentRemovalRecordAttributes; attributes: AppDeploymentRemovalRecordAttributes;
} }
export interface AddProjectFromTemplateInput {
templateOwner: string;
templateRepo: string;
owner: string;
name: string;
isPrivate: boolean;
}
@@ -19,6 +19,6 @@ record:
LACONIC_HOSTED_CONFIG_turnkey_api_base_url: https://api.turnkey.com LACONIC_HOSTED_CONFIG_turnkey_api_base_url: https://api.turnkey.com
LACONIC_HOSTED_CONFIG_turnkey_organization_id: 5049ae99-5bca-40b3-8317-504384d4e591 LACONIC_HOSTED_CONFIG_turnkey_organization_id: 5049ae99-5bca-40b3-8317-504384d4e591
meta: meta:
note: Added by Snowball @ Sat Jun 22 02:49:45 UTC 2024 note: Added by Snowball @ Mon Jun 24 23:51:48 UTC 2024
repository: "https://git.vdb.to/cerc-io/snowballtools-base" repository: "https://git.vdb.to/cerc-io/snowballtools-base"
repository_ref: 61e3e88a6c9d57e95441059369ee5a46f5c07601 repository_ref: 61e3e88a6c9d57e95441059369ee5a46f5c07601
+3 -1
View File
@@ -44,7 +44,9 @@ yarn dev
## Deployment ## Deployment
From the root of the project, run: From the root of the project,
- check if the [gittea repository mirror](https://git.vdb.to/cerc-io/snowballtools-base) is up to date
### Staging ### Staging
-1
View File
@@ -62,7 +62,6 @@
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-dropdown": "^1.11.0", "react-dropdown": "^1.11.0",
"react-hook-form": "^7.49.0", "react-hook-form": "^7.49.0",
"react-hot-toast": "^2.4.1",
"react-oauth-popup": "^1.0.5", "react-oauth-popup": "^1.0.5",
"react-router-dom": "^6.20.1", "react-router-dom": "^6.20.1",
"react-timer-hook": "^3.0.7", "react-timer-hook": "^3.0.7",
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { useCombobox } from 'downshift'; import { useCombobox } from 'downshift';
import { Project } from 'gql-client'; import { Project } from 'gql-client';
import { useDebounce } from 'usehooks-ts'; import { useDebounceValue } from 'usehooks-ts';
import SearchBar from 'components/SearchBar'; import SearchBar from 'components/SearchBar';
import { useGQLClient } from 'context/GQLClientContext'; import { useGQLClient } from 'context/GQLClientContext';
@@ -42,7 +42,7 @@ export const ProjectSearchBar = ({ onChange }: ProjectSearchBarProps) => {
}, },
}); });
const debouncedInputValue = useDebounce<string>(inputValue, 300); const [debouncedInputValue, _] = useDebounceValue<string>(inputValue, 300);
const fetchProjects = useCallback( const fetchProjects = useCallback(
async (inputValue: string) => { async (inputValue: string) => {
@@ -62,7 +62,7 @@ export const ProjectSearchBar = ({ onChange }: ProjectSearchBarProps) => {
<div className="relative w-full lg:w-fit"> <div className="relative w-full lg:w-fit">
<SearchBar {...getInputProps()} /> <SearchBar {...getInputProps()} />
<div <div
{...getMenuProps()} {...getMenuProps({}, { suppressRefError: true })}
className={cn( className={cn(
'flex flex-col shadow-dropdown rounded-xl bg-surface-card absolute w-[459px] max-h-52 overflow-y-auto px-2 py-2 gap-1 z-50', 'flex flex-col shadow-dropdown rounded-xl bg-surface-card absolute w-[459px] max-h-52 overflow-y-auto px-2 py-2 gap-1 z-50',
{ hidden: !inputValue || !isOpen }, { hidden: !inputValue || !isOpen },
@@ -5,7 +5,7 @@ import { CrossIcon, SearchIcon } from 'components/shared/CustomIcon';
import { Input } from 'components/shared/Input'; import { Input } from 'components/shared/Input';
import { useGQLClient } from 'context/GQLClientContext'; import { useGQLClient } from 'context/GQLClientContext';
import { Project } from 'gql-client'; import { Project } from 'gql-client';
import { useDebounce } from 'usehooks-ts'; import { useDebounceValue } from 'usehooks-ts';
import { ProjectSearchBarItem } from './ProjectSearchBarItem'; import { ProjectSearchBarItem } from './ProjectSearchBarItem';
import { ProjectSearchBarEmpty } from './ProjectSearchBarEmpty'; import { ProjectSearchBarEmpty } from './ProjectSearchBarEmpty';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
@@ -27,25 +27,30 @@ export const ProjectSearchBarDialog = ({
const client = useGQLClient(); const client = useGQLClient();
const navigate = useNavigate(); const navigate = useNavigate();
const { getInputProps, getItemProps, inputValue, setInputValue } = const {
useCombobox({ getInputProps,
items, getItemProps,
itemToString(item) { getMenuProps,
return item ? item.name : ''; inputValue,
}, setInputValue,
selectedItem, } = useCombobox({
onSelectedItemChange: ({ selectedItem: newSelectedItem }) => { items,
if (newSelectedItem) { itemToString(item) {
setSelectedItem(newSelectedItem); return item ? item.name : '';
onClickItem?.(newSelectedItem); },
navigate( selectedItem,
`/${newSelectedItem.organization.slug}/projects/${newSelectedItem.id}`, onSelectedItemChange: ({ selectedItem: newSelectedItem }) => {
); if (newSelectedItem) {
} setSelectedItem(newSelectedItem);
}, onClickItem?.(newSelectedItem);
}); navigate(
`/${newSelectedItem.organization.slug}/projects/${newSelectedItem.id}`,
);
}
},
});
const debouncedInputValue = useDebounce<string>(inputValue, 300); const [debouncedInputValue, _] = useDebounceValue<string>(inputValue, 300);
const fetchProjects = useCallback( const fetchProjects = useCallback(
async (inputValue: string) => { async (inputValue: string) => {
@@ -75,7 +80,7 @@ export const ProjectSearchBarDialog = ({
<div className="h-full flex flex-col fixed top-0 inset-0"> <div className="h-full flex flex-col fixed top-0 inset-0">
<div className="py-2.5 px-4 flex items-center justify-between border-b border-border-separator/[0.06]"> <div className="py-2.5 px-4 flex items-center justify-between border-b border-border-separator/[0.06]">
<Input <Input
{...getInputProps()} {...getInputProps({}, { suppressRefError: true })}
leftIcon={<SearchIcon />} leftIcon={<SearchIcon />}
placeholder="Search" placeholder="Search"
appearance="borderless" appearance="borderless"
@@ -86,23 +91,33 @@ export const ProjectSearchBarDialog = ({
</Button> </Button>
</div> </div>
{/* Content */} {/* Content */}
<div className="flex flex-col gap-1 px-2 py-2"> <div
{items.length > 0 className="flex flex-col gap-1 px-2 py-2"
? items.map((item, index) => ( {...getMenuProps(
<> {},
<div className="px-2 py-2"> {
<p className="text-elements-mid-em text-xs font-medium"> suppressRefError: true,
Suggestions },
</p> )}
</div> >
<ProjectSearchBarItem {items.length > 0 ? (
key={item.id} <>
item={item} <div className="px-2 py-2">
{...getItemProps({ item, index })} <p className="text-elements-mid-em text-xs font-medium">
/> Suggestions
</> </p>
)) </div>
: inputValue && <ProjectSearchBarEmpty />} {items.map((item, index) => (
<ProjectSearchBarItem
key={item.id}
item={item}
{...getItemProps({ item, index })}
/>
))}
</>
) : (
inputValue && <ProjectSearchBarEmpty />
)}
</div> </div>
</div> </div>
</Dialog.Content> </Dialog.Content>
@@ -2,7 +2,6 @@ import { useState } from 'react';
import { Domain, DomainStatus, Project } from 'gql-client'; import { Domain, DomainStatus, Project } from 'gql-client';
import { import {
Chip,
Typography, Typography,
Menu, Menu,
MenuHandler, MenuHandler,
@@ -15,6 +14,15 @@ import EditDomainDialog from './EditDomainDialog';
import { useGQLClient } from 'context/GQLClientContext'; import { useGQLClient } from 'context/GQLClientContext';
import { DeleteDomainDialog } from 'components/projects/Dialog/DeleteDomainDialog'; import { DeleteDomainDialog } from 'components/projects/Dialog/DeleteDomainDialog';
import { useToast } from 'components/shared/Toast'; import { useToast } from 'components/shared/Toast';
import { Tag } from 'components/shared/Tag';
import {
CheckIcon,
CrossIcon,
GearIcon,
LoadingIcon,
} from 'components/shared/CustomIcon';
import { Heading } from 'components/shared/Heading';
import { Button } from 'components/shared/Button';
enum RefreshStatus { enum RefreshStatus {
IDLE, IDLE,
@@ -79,22 +87,29 @@ const DomainCard = ({
<> <>
<div className="flex justify-between py-3"> <div className="flex justify-between py-3">
<div className="flex justify-start gap-1"> <div className="flex justify-start gap-1">
<Typography variant="h6"> <Heading as="h6" className="flex-col">
<i>^</i> {domain.name} {domain.name}{' '}
</Typography> <Tag
<Chip type={
className="w-fit capitalize" domain.status === DomainStatus.Live ? 'positive' : 'negative'
value={domain.status} }
color={domain.status === DomainStatus.Live ? 'green' : 'orange'} leftIcon={
variant="ghost" domain.status === DomainStatus.Live ? (
icon={<i>^</i>} <CheckIcon />
/> ) : (
<CrossIcon />
)
}
>
{domain.status}
</Tag>
</Heading>
</div> </div>
<div className="flex justify-start gap-1"> <div className="flex justify-start gap-1">
<i <i
id="refresh" id="refresh"
className="cursor-pointer w-8 h-8" className="cursor-pointer"
onClick={() => { onClick={() => {
SetRefreshStatus(RefreshStatus.CHECKING); SetRefreshStatus(RefreshStatus.CHECKING);
setTimeout(() => { setTimeout(() => {
@@ -102,11 +117,17 @@ const DomainCard = ({
}, CHECK_FAIL_TIMEOUT); }, CHECK_FAIL_TIMEOUT);
}} }}
> >
{refreshStatus === RefreshStatus.CHECKING ? 'L' : 'R'} {refreshStatus === RefreshStatus.CHECKING ? (
<LoadingIcon className="animate-spin" />
) : (
'L'
)}
</i> </i>
<Menu placement="bottom-end"> <Menu placement="bottom-end">
<MenuHandler> <MenuHandler>
<button className="border-2 rounded-full w-8 h-8">...</button> <Button iconOnly>
<GearIcon />
</Button>
</MenuHandler> </MenuHandler>
<MenuList> <MenuList>
<MenuItem <MenuItem
@@ -143,13 +164,13 @@ const DomainCard = ({
{domain.status === DomainStatus.Pending && ( {domain.status === DomainStatus.Pending && (
<Card className="bg-slate-100 p-4 text-sm"> <Card className="bg-slate-100 p-4 text-sm">
{refreshStatus === RefreshStatus.IDLE ? ( {refreshStatus === RefreshStatus.IDLE ? (
<Typography variant="small"> <Heading>
^ Add these records to your domain and refresh to check ^ Add these records to your domain and refresh to check
</Typography> </Heading>
) : refreshStatus === RefreshStatus.CHECKING ? ( ) : refreshStatus === RefreshStatus.CHECKING ? (
<Typography variant="small" className="text-blue-500"> <Heading className="text-blue-500">
^ Checking records for {domain.name} ^ Checking records for {domain.name}
</Typography> </Heading>
) : ( ) : (
<div className="flex gap-2 text-red-500 mb-2"> <div className="flex gap-2 text-red-500 mb-2">
<div className="grow"> <div className="grow">
@@ -1,6 +1,5 @@
import { useCallback, useEffect, useMemo } from 'react'; import { useCallback, useEffect, useMemo } from 'react';
import { Controller, useForm, SubmitHandler } from 'react-hook-form'; import { Controller, useForm, SubmitHandler } from 'react-hook-form';
import toast from 'react-hot-toast';
import { Domain } from 'gql-client'; import { Domain } from 'gql-client';
import { import {
@@ -9,10 +8,11 @@ import {
Option, Option,
} from '@snowballtools/material-tailwind-react-fork'; } from '@snowballtools/material-tailwind-react-fork';
import { useGQLClient } from '../../../../context/GQLClientContext'; import { useGQLClient } from 'context/GQLClientContext';
import { Modal } from 'components/shared/Modal'; import { Modal } from 'components/shared/Modal';
import { Button } from 'components/shared/Button'; import { Button } from 'components/shared/Button';
import { Input } from 'components/shared/Input'; import { Input } from 'components/shared/Input';
import { useToast } from 'components/shared/Toast';
const DEFAULT_REDIRECT_OPTIONS = ['none']; const DEFAULT_REDIRECT_OPTIONS = ['none'];
@@ -40,6 +40,7 @@ const EditDomainDialog = ({
onUpdate, onUpdate,
}: EditDomainDialogProp) => { }: EditDomainDialogProp) => {
const client = useGQLClient(); const client = useGQLClient();
const { toast, dismiss } = useToast();
const getRedirectUrl = (domain: Domain) => { const getRedirectUrl = (domain: Domain) => {
const redirectDomain = domain.redirectTo; const redirectDomain = domain.redirectTo;
@@ -99,10 +100,20 @@ const EditDomainDialog = ({
if (updateDomain) { if (updateDomain) {
await onUpdate(); await onUpdate();
toast.success(`Domain ${domain.name} has been updated`); toast({
id: 'domain_id_updated',
title: `Domain ${domain.name} has been updated`,
variant: 'success',
onDismiss: dismiss,
});
} else { } else {
reset(); reset();
toast.error(`Error updating domain ${domain.name}`); toast({
id: 'domain_id_error_update',
title: `Error updating domain ${domain.name}`,
variant: 'error',
onDismiss: dismiss,
});
} }
handleOpen(); handleOpen();
@@ -3,6 +3,8 @@ import { useState } from 'react';
import { DeleteWebhookDialog } from 'components/projects/Dialog/DeleteWebhookDialog'; import { DeleteWebhookDialog } from 'components/projects/Dialog/DeleteWebhookDialog';
import { Button } from 'components/shared/Button'; import { Button } from 'components/shared/Button';
import { useToast } from 'components/shared/Toast'; import { useToast } from 'components/shared/Toast';
import { Input } from 'components/shared/Input';
import { CopyIcon, TrashIcon } from 'components/shared/CustomIcon';
interface WebhookCardProps { interface WebhookCardProps {
webhookUrl: string; webhookUrl: string;
@@ -14,11 +16,12 @@ const WebhookCard = ({ webhookUrl, onDelete }: WebhookCardProps) => {
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
return ( return (
<div className="flex justify-between w-full mb-3"> <div className="flex justify-between w-full mb-3 gap-3">
{webhookUrl} <Input value={webhookUrl} disabled />
<div className="flex gap-3"> <div className="flex gap-3">
<Button <Button
size="sm" iconOnly
size="md"
onClick={() => { onClick={() => {
navigator.clipboard.writeText(webhookUrl); navigator.clipboard.writeText(webhookUrl);
toast({ toast({
@@ -29,16 +32,17 @@ const WebhookCard = ({ webhookUrl, onDelete }: WebhookCardProps) => {
}); });
}} }}
> >
Copy <CopyIcon />
</Button> </Button>
<Button <Button
size="sm" iconOnly
size="md"
variant="danger" variant="danger"
onClick={() => { onClick={() => {
setDeleteDialogOpen(true); setDeleteDialogOpen(true);
}} }}
> >
X <TrashIcon />
</Button> </Button>
</div> </div>
<DeleteWebhookDialog <DeleteWebhookDialog
@@ -0,0 +1,27 @@
export { Avatar } from './Avatar';
export { Badge } from './Badge';
export { Button } from './Button';
export { Calendar } from './Calendar';
export { Checkbox } from './Checkbox';
export { DatePicker } from './DatePicker';
export { DotBorder } from './DotBorder';
export { Heading } from './Heading';
export { IconWithFrame } from './IconWithFrame';
export { InlineNotification } from './InlineNotification';
export { Input } from './Input';
export { Modal } from './Modal';
export { OverflownText } from './OverflownText';
export { Radio } from './Radio';
export { SegmentedControls } from './SegmentedControls';
export { Select } from './Select';
export { Sidebar } from './Sidebar';
export { Step } from './Steps';
export { Switch } from './Switch';
export { Table } from './Table';
export { Tabs } from './Tabs';
export { Tag } from './Tag';
export { useToast } from './Toast';
export { Tooltip } from './Tooltip';
export { UserSelect } from './UserSelect';
export { VerifyCodeInput } from './VerifyCodeInput';
export { WavyBorder } from './WavyBorder';
+1 -2
View File
@@ -16,8 +16,7 @@ import { Toaster } from 'components/shared/Toast';
import { LogErrorBoundary } from 'utils/log-error'; import { LogErrorBoundary } from 'utils/log-error';
import { baseUrl } from 'utils/constants'; import { baseUrl } from 'utils/constants';
// @ts-ignore console.log(`v-0.0.9`);
console.log(`v-${__VERSION__}`);
const root = ReactDOM.createRoot( const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement, document.getElementById('root') as HTMLElement,
@@ -32,7 +32,7 @@ const ProjectSearch = () => {
return ( return (
<section className="h-full flex flex-col"> <section className="h-full flex flex-col">
{/* Header */} {/* Header */}
<div className="sticky hidden lg:block top-0 border-b bg-base-bg border-border-separator/[0.06] z-30"> <div className="sticky hidden lg:block top-0 border-b bg-base-bg border-border-separator/[0.06] hover:z-30">
<div className="flex pr-6 pl-2 py-2 items-center"> <div className="flex pr-6 pl-2 py-2 items-center">
<div className="flex-1"> <div className="flex-1">
<ProjectSearchBar <ProjectSearchBar
@@ -1,5 +1,19 @@
const Settings = () => { import { Heading } from 'components/shared/Heading';
return <div className="p-5">Settings page</div>;
};
const Settings = () => {
return (
<section className="px-4 md:px-6 py-6 flex flex-col gap-6">
{/* Header */}
<div className="flex items-center">
<div className="grow">
<div className="flex gap-4 items-center">
<Heading as="h2" className="text-[24px]">
Settings
</Heading>
</div>
</div>
</div>
</section>
);
};
export default Settings; export default Settings;
@@ -1,13 +1,11 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { Link, useParams } from 'react-router-dom'; import { Link, useParams } from 'react-router-dom';
import { Project } from 'gql-client';
import { Button } from 'components/shared/Button';
import { PlusIcon } from 'components/shared/CustomIcon';
import { ProjectCard } from 'components/projects/ProjectCard'; import { ProjectCard } from 'components/projects/ProjectCard';
import { Heading } from 'components/shared/Heading'; import { Heading, Badge, Button } from 'components/shared';
import { Badge } from 'components/shared/Badge'; import { PlusIcon } from 'components/shared/CustomIcon';
import { useGQLClient } from 'context/GQLClientContext'; import { useGQLClient } from 'context/GQLClientContext';
import { Project } from 'gql-client';
const Projects = () => { const Projects = () => {
const client = useGQLClient(); const client = useGQLClient();
@@ -28,6 +26,7 @@ const Projects = () => {
return ( return (
<section className="px-4 md:px-6 py-6 flex flex-col gap-6"> <section className="px-4 md:px-6 py-6 flex flex-col gap-6">
{/* Header */} {/* Header */}
<div className="flex items-center"> <div className="flex items-center">
<div className="grow"> <div className="grow">
<div className="flex gap-4 items-center"> <div className="flex gap-4 items-center">
@@ -11,98 +11,128 @@ import {
import { Heading } from 'components/shared/Heading'; import { Heading } from 'components/shared/Heading';
import logoAnimation from 'components/../../public/lottie/logo.json'; import logoAnimation from 'components/../../public/lottie/logo.json';
import { useGQLClient } from 'context/GQLClientContext';
import { Project } from 'gql-client';
import { useEffect, useState } from 'react';
const Id = () => { const Id = () => {
const { id, orgSlug } = useParams(); const { id, orgSlug } = useParams();
const client = useGQLClient();
const [project, setProject] = useState<Project | null>(null);
const handleSetupDomain = () => { const handleSetupDomain = async () => {
//TODO: Implement this if (id) {
// console.log('id', id);
// console.log('getting project for id', id);
const project = await client.getProject(id);
// console.log('project found:', project);
if (project && project.project) {
// console.log('project:', project.project);
setProject(project.project);
}
} else {
window.location.href = '/';
}
}; };
useEffect(() => {
handleSetupDomain();
});
return ( return (
<div className="flex flex-col gap-8 lg:gap-11 max-w-[522px] mx-auto py-6 lg:py-12"> <>
{/* Icon */} {project ? (
<div className="flex justify-center"> <div className="flex flex-col gap-8 lg:gap-11 max-w-[522px] mx-auto py-6 lg:py-12">
<Lottie animationData={logoAnimation} loop={false} size={40} /> {/* Icon */}
</div> <div className="flex justify-center">
<Lottie animationData={logoAnimation} loop={false} size={40} />
{/* Heading */}
<div className="flex flex-col items-center gap-1.5">
<Heading as="h3" className="font-medium text-xl">
Project deployed successfully.
</Heading>
<p className="flex flex-col items-center lg:flex-row font-sans gap-0.5 lg:gap-2 text-sm text-elements-high-em">
Your project has been deployed at{' '}
<Button
className="no-underline text-elements-link"
// TODO: use dynamic value
href="https://www.iglootools.snowballtools.xyz"
as="a"
variant="link-emphasized"
external
leftIcon={<LinkChainIcon />}
>
{/* // TODO: use dynamic value */}
www.iglootools.snowballtools.xyz
</Button>
</p>
</div>
{/* Card */}
<div className="bg-base-bg-alternate rounded-xl shadow-inset w-full px-1 py-1">
{/* Trigger question */}
<div className="flex gap-2 justify-center items-center py-3">
<div className="h-5 w-5">
<QuestionMarkRoundFilledIcon size={18} />
</div> </div>
<Heading as="h5" className="font-sans font-medium text-sm">
{`Wondering what's next?`}
</Heading>
</div>
{/* CTA card */} {/* Heading */}
<div className="bg-surface-card rounded-xl shadow-card-sm px-4 py-4"> <div className="flex flex-col items-center gap-1.5">
<div className="flex gap-2"> <Heading as="h3" className="font-medium text-xl">
<Badge variant="secondary">1</Badge> Project deployed successfully.
<div className="space-y-3"> </Heading>
<div className="flex flex-col gap-1"> <p className="flex flex-col items-center lg:flex-row font-sans gap-0.5 lg:gap-2 text-sm text-elements-high-em">
<Heading as="h6" className="text-sm font-sans"> Your project has been deployed at{' '}
Add a custom domain <Button
</Heading> className="no-underline text-elements-link"
<p className="text-xs text-elements-low-em font-sans"> // TODO: use dynamic value
Make it easy for your visitors to remember your URL with a href={project ? `https://${project.subDomain}` : ''}
custom domain. as="a"
</p> variant="link-emphasized"
</div> external
<Button onClick={handleSetupDomain} variant="tertiary" size="sm"> leftIcon={<LinkChainIcon />}
Setup domain >
{project.subDomain}
</Button> </Button>
</p>
</div>
{/* Card */}
<div className="bg-base-bg-alternate rounded-xl shadow-inset w-full px-1 py-1">
{/* Trigger question */}
<div className="flex gap-2 justify-center items-center py-3">
<div className="h-5 w-5">
<QuestionMarkRoundFilledIcon size={18} />
</div>
<Heading as="h5" className="font-sans font-medium text-sm">
{`Wondering what's next?`}
</Heading>
</div>
{/* CTA card */}
<div className="bg-surface-card rounded-xl shadow-card-sm px-4 py-4">
<div className="flex gap-2">
<Badge variant="secondary">1</Badge>
<div className="space-y-3">
<div className="flex flex-col gap-1">
<Heading as="h6" className="text-sm font-sans">
Add a custom domain
</Heading>
<p className="text-xs text-elements-low-em font-sans">
Make it easy for your visitors to remember your URL with a
custom domain.
</p>
</div>
<Button
onClick={handleSetupDomain}
variant="tertiary"
size="sm"
>
Setup domain
</Button>
</div>
</div>
</div>
</div>
{/* CTA Buttons */}
<div className="flex flex-col lg:flex-row justify-center gap-3">
<div className="w-full lg:w-fit">
<Link to="/">
<Button
leftIcon={<ArrowLeftCircleFilledIcon />}
fullWidth
variant="tertiary"
>
Back to projects
</Button>
</Link>
</div>
<div className="w-full lg:w-fit">
<Link to={`/${orgSlug}/projects/${id}`}>
<Button fullWidth variant="primary">
View project
</Button>
</Link>
</div> </div>
</div> </div>
</div> </div>
</div> ) : (
<></>
{/* CTA Buttons */} )}
<div className="flex flex-col lg:flex-row justify-center gap-3"> </>
<div className="w-full lg:w-fit">
<Link to="/">
<Button
leftIcon={<ArrowLeftCircleFilledIcon />}
fullWidth
variant="tertiary"
>
Back to projects
</Button>
</Link>
</div>
<div className="w-full lg:w-fit">
<Link to={`/${orgSlug}/projects/${id}`}>
<Button fullWidth variant="primary">
View project
</Button>
</Link>
</div>
</div>
</div>
); );
}; };
@@ -11,7 +11,10 @@ import { Template } from '../../../../../types/types';
import { Heading } from 'components/shared/Heading'; import { Heading } from 'components/shared/Heading';
import { Input } from 'components/shared/Input'; import { Input } from 'components/shared/Input';
import { Select, SelectOption } from 'components/shared/Select'; import { Select, SelectOption } from 'components/shared/Select';
import { ArrowRightCircleFilledIcon } from 'components/shared/CustomIcon'; import {
ArrowRightCircleFilledIcon,
LoadingIcon,
} from 'components/shared/CustomIcon';
import { Checkbox } from 'components/shared/Checkbox'; import { Checkbox } from 'components/shared/Checkbox';
import { Button } from 'components/shared/Button'; import { Button } from 'components/shared/Button';
import { useToast } from 'components/shared/Toast'; import { useToast } from 'components/shared/Toast';
@@ -50,36 +53,22 @@ const CreateRepo = () => {
assert(template.repoFullName, 'Template URL not provided'); assert(template.repoFullName, 'Template URL not provided');
const [owner, repo] = template.repoFullName.split('/'); const [owner, repo] = template.repoFullName.split('/');
// TODO: Handle this functionality in backend setIsLoading(true);
const gitRepo = await octokit?.rest.repos.createUsingTemplate({
template_owner: owner,
template_repo: repo,
owner: data.account,
name: data.repoName,
include_all_branches: false,
private: data.isPrivate,
});
if (!gitRepo) { const { addProjectFromTemplate } = await client.addProjectFromTemplate(
return; orgSlug!,
} {
templateOwner: owner,
templateRepo: repo,
owner: data.account,
name: data.repoName,
isPrivate: false,
},
);
// Refetch to always get correct default branch navigate(
const templateRepo = await octokit.rest.repos.get({ `deploy?projectId=${addProjectFromTemplate.id}&templateId=${template.id}`,
owner: template.repoFullName.split('/')[0], );
repo: template.repoFullName.split('/')[1],
});
const prodBranch = templateRepo.data.default_branch ?? 'main';
const { addProject } = await client.addProject(orgSlug!, {
name: `${gitRepo.data.owner!.login}-${gitRepo.data.name}`,
prodBranch,
repository: gitRepo.data.full_name,
// TODO: Set selected template
template: 'webapp',
});
navigate(`deploy?projectId=${addProject.id}&templateId=${template.id}`);
} catch (err) { } catch (err) {
setIsLoading(false); setIsLoading(false);
@@ -206,7 +195,13 @@ const CreateRepo = () => {
{...buttonSize} {...buttonSize}
type="submit" type="submit"
disabled={!Boolean(template.repoFullName) || isLoading} disabled={!Boolean(template.repoFullName) || isLoading}
rightIcon={<ArrowRightCircleFilledIcon />} rightIcon={
isLoading ? (
<LoadingIcon className="animate-spin" />
) : (
<ArrowRightCircleFilledIcon />
)
}
> >
Deploy Deploy
</Button> </Button>
@@ -1,14 +1,11 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Domain, DomainStatus } from 'gql-client';
import { Link, useNavigate, useOutletContext } from 'react-router-dom'; import { Link, useNavigate, useOutletContext } from 'react-router-dom';
import { RequestError } from 'octokit'; import { RequestError } from 'octokit';
import { useOctokit } from '../../../../context/OctokitContext'; import { useOctokit } from 'context/OctokitContext';
import { GitCommitWithBranch, OutletContextType } from '../../../../types'; import { GitCommitWithBranch, OutletContextType } from '../../../../types';
import { useGQLClient } from '../../../../context/GQLClientContext'; import { useGQLClient } from 'context/GQLClientContext';
import { Button } from 'components/shared/Button'; import { Button, Heading, Avatar, Tag } from 'components/shared';
import { Heading } from 'components/shared/Heading';
import { Avatar } from 'components/shared/Avatar';
import { getInitials } from 'utils/geInitials'; import { getInitials } from 'utils/geInitials';
import { import {
BranchStrokeIcon, BranchStrokeIcon,
@@ -18,12 +15,12 @@ import {
GithubStrokeIcon, GithubStrokeIcon,
GlobeIcon, GlobeIcon,
LinkIcon, LinkIcon,
CalendarDaysIcon,
} from 'components/shared/CustomIcon'; } from 'components/shared/CustomIcon';
import { Tag } from 'components/shared/Tag';
import { Activity } from 'components/projects/project/overview/Activity'; import { Activity } from 'components/projects/project/overview/Activity';
import { OverviewInfo } from 'components/projects/project/overview/OverviewInfo'; import { OverviewInfo } from 'components/projects/project/overview/OverviewInfo';
import { CalendarDaysIcon } from 'components/shared/CustomIcon/CalendarDaysIcon';
import { relativeTimeMs } from 'utils/time'; import { relativeTimeMs } from 'utils/time';
import { Domain, DomainStatus } from 'gql-client';
const COMMITS_PER_PAGE = 4; const COMMITS_PER_PAGE = 4;
@@ -131,9 +128,12 @@ const OverviewTabPanel = () => {
<Heading className="text-lg leading-6 font-medium truncate"> <Heading className="text-lg leading-6 font-medium truncate">
{project.name} {project.name}
</Heading> </Heading>
<p className="text-sm text-elements-low-em tracking-tight truncate"> <a
href={`https://${project.subDomain}`}
className="text-sm text-elements-low-em tracking-tight truncate"
>
{project.subDomain} {project.subDomain}
</p> </a>
</div> </div>
</div> </div>
<OverviewInfo label="Domain" icon={<GlobeIcon />}> <OverviewInfo label="Domain" icon={<GlobeIcon />}>
@@ -1,14 +1,12 @@
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { useFieldArray, useForm } from 'react-hook-form'; import { useFieldArray, useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { Environment, EnvironmentVariable } from 'gql-client';
import { Collapse } from '@snowballtools/material-tailwind-react-fork'; import { Collapse } from '@snowballtools/material-tailwind-react-fork';
import AddEnvironmentVariableRow from 'components/projects/project/settings/AddEnvironmentVariableRow'; import AddEnvironmentVariableRow from 'components/projects/project/settings/AddEnvironmentVariableRow';
import DisplayEnvironmentVariables from 'components/projects/project/settings/DisplayEnvironmentVariables'; import DisplayEnvironmentVariables from 'components/projects/project/settings/DisplayEnvironmentVariables';
import { useGQLClient } from '../../../../../context/GQLClientContext'; import { useGQLClient } from 'context/GQLClientContext';
import { EnvironmentVariablesFormValues } from '../../../../../types'; import { EnvironmentVariablesFormValues } from '../../../../../types';
import HorizontalLine from 'components/HorizontalLine'; import HorizontalLine from 'components/HorizontalLine';
import { Heading } from 'components/shared/Heading'; import { Heading } from 'components/shared/Heading';
@@ -17,10 +15,13 @@ import { Checkbox } from 'components/shared/Checkbox';
import { PlusIcon } from 'components/shared/CustomIcon'; import { PlusIcon } from 'components/shared/CustomIcon';
import { InlineNotification } from 'components/shared/InlineNotification'; import { InlineNotification } from 'components/shared/InlineNotification';
import { ProjectSettingContainer } from 'components/projects/project/settings/ProjectSettingContainer'; import { ProjectSettingContainer } from 'components/projects/project/settings/ProjectSettingContainer';
import { useToast } from 'components/shared/Toast';
import { Environment, EnvironmentVariable } from 'gql-client';
export const EnvironmentVariablesTabPanel = () => { export const EnvironmentVariablesTabPanel = () => {
const { id } = useParams(); const { id } = useParams();
const client = useGQLClient(); const client = useGQLClient();
const { toast, dismiss } = useToast();
const [environmentVariables, setEnvironmentVariables] = useState< const [environmentVariables, setEnvironmentVariables] = useState<
EnvironmentVariable[] EnvironmentVariable[]
@@ -118,13 +119,25 @@ export const EnvironmentVariablesTabPanel = () => {
fetchEnvironmentVariables(id); fetchEnvironmentVariables(id);
toast.success( toast({
createFormData.variables.length > 1 id:
? `${createFormData.variables.length} variables added` createFormData.variables.length > 1
: `Variable added`, ? 'env_variable_added'
); : 'env_variables_added',
title:
createFormData.variables.length > 1
? `${createFormData.variables.length} variables added`
: `Variable added`,
variant: 'success',
onDismiss: dismiss,
});
} else { } else {
toast.error('Environment variables not added'); toast({
id: 'env_variables_not_added',
title: 'Environment variables not added',
variant: 'error',
onDismiss: dismiss,
});
} }
}, },
[id, client], [id, client],
@@ -1,12 +1,12 @@
import toast from 'react-hot-toast';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { useGQLClient } from '../../../../../../../context/GQLClientContext'; import { useGQLClient } from 'context/GQLClientContext';
import { Table } from 'components/shared/Table'; import { Table } from 'components/shared/Table';
import { Button } from 'components/shared/Button'; import { Button } from 'components/shared/Button';
import { InlineNotification } from 'components/shared/InlineNotification'; import { InlineNotification } from 'components/shared/InlineNotification';
import { ArrowRightCircleIcon } from 'components/shared/CustomIcon'; import { ArrowRightCircleIcon } from 'components/shared/CustomIcon';
import { ProjectSettingContainer } from 'components/projects/project/settings/ProjectSettingContainer'; import { ProjectSettingContainer } from 'components/projects/project/settings/ProjectSettingContainer';
import { useToast } from 'components/shared/Toast';
const Config = () => { const Config = () => {
const { id, orgSlug } = useParams(); const { id, orgSlug } = useParams();
@@ -14,15 +14,26 @@ const Config = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const primaryDomainName = searchParams.get('name'); const primaryDomainName = searchParams.get('name');
const { toast, dismiss } = useToast();
const handleSubmitDomain = async () => { const handleSubmitDomain = async () => {
if (primaryDomainName === null) { if (primaryDomainName === null) {
toast.error('Cannot resolve domain name'); toast({
id: 'unresolvable_domain_name',
title: 'Cannot resolve domain name',
variant: 'error',
onDismiss: dismiss,
});
return; return;
} }
if (id === undefined) { if (id === undefined) {
toast.error('Cannot find project'); toast({
id: 'domain_cannot_find_project',
title: 'Cannot find project',
variant: 'error',
onDismiss: dismiss,
});
return; return;
} }
@@ -31,10 +42,20 @@ const Config = () => {
}); });
if (isAdded) { if (isAdded) {
toast.success('Domain added successfully'); toast({
id: 'domain_added_successfully',
title: 'Domain added successfully',
variant: 'success',
onDismiss: dismiss,
});
navigate(`/${orgSlug}/projects/${id}/settings/domains`); navigate(`/${orgSlug}/projects/${id}/settings/domains`);
} else { } else {
toast.error('Error adding domain'); toast({
id: 'generic_error_adding_domain',
title: 'Error adding domaint',
variant: 'error',
onDismiss: dismiss,
});
} }
}; };
@@ -0,0 +1,20 @@
import { StoryObj, Meta } from '@storybook/react';
import { ProjectSearchBar } from 'components/projects/ProjectSearchBar';
const meta: Meta<typeof ProjectSearchBar> = {
title: 'Components/ProjectSearchBar',
component: ProjectSearchBar,
tags: ['autodocs'],
argTypes: {
onChange: {
action: 'change',
},
},
};
export default meta;
type Story = StoryObj<typeof ProjectSearchBar>;
export const Default: Story = {};
@@ -11,6 +11,7 @@ import {
DomainStatus, DomainStatus,
Domain, Domain,
Environment, Environment,
Permission,
} from 'gql-client'; } from 'gql-client';
export const user: User = { export const user: User = {
@@ -44,7 +45,7 @@ export const organization: Organization = {
export const member: ProjectMember = { export const member: ProjectMember = {
id: '1', id: '1',
member: user, member: user,
permissions: [], permissions: [Permission.Edit],
isPending: false, isPending: false,
createdAt: '2021-08-01T00:00:00.000Z', createdAt: '2021-08-01T00:00:00.000Z',
updatedAt: '2021-08-01T00:00:00.000Z', updatedAt: '2021-08-01T00:00:00.000Z',
@@ -70,7 +71,7 @@ export const environmentVariable1: EnvironmentVariable = {
export const domain0: Domain = { export const domain0: Domain = {
id: '1', id: '1',
name: 'Domain', name: 'domain.com',
createdAt: '2021-08-01T00:00:00.000Z', createdAt: '2021-08-01T00:00:00.000Z',
updatedAt: '2021-08-01T00:00:00.000Z', updatedAt: '2021-08-01T00:00:00.000Z',
branch: 'Branch', branch: 'Branch',
@@ -78,6 +79,16 @@ export const domain0: Domain = {
redirectTo: null, redirectTo: null,
}; };
export const domain1: Domain = {
id: '2',
name: 'www.domain.com',
createdAt: '2021-08-01T00:00:00.000Z',
updatedAt: '2021-08-01T00:00:00.000Z',
branch: 'Branch',
status: DomainStatus.Live,
redirectTo: domain0,
};
export const deployment0: Deployment = { export const deployment0: Deployment = {
id: '1', id: '1',
url: 'https://deployment.com', url: 'https://deployment.com',
@@ -0,0 +1,48 @@
import { StoryObj, Meta } from '@storybook/react';
import DomainCard from 'components/projects/project/settings/DomainCard';
import { domain0, domain1, project } from '../../MockStoriesData';
const meta: Meta<typeof DomainCard> = {
title: 'Project/Settings/DomainCard',
component: DomainCard,
tags: ['autodocs'],
argTypes: {
domains: {
control: {
type: 'object',
},
},
domain: {
control: {
type: 'object',
},
},
branches: {
control: {
type: 'object',
},
},
project: {
control: {
type: 'object',
},
},
onUpdate: {
action: 'update',
},
},
};
export default meta;
type Story = StoryObj<typeof DomainCard>;
export const Default: Story = {
args: {
domains: [domain0, domain1],
domain: domain0,
branches: ['main'],
project: project,
},
};
@@ -0,0 +1,43 @@
import { StoryObj, Meta } from '@storybook/react';
import EditDomainDialog from 'components/projects/project/settings/EditDomainDialog';
const meta: Meta<typeof EditDomainDialog> = {
title: 'Components/EditDomainDialog',
component: EditDomainDialog,
tags: ['autodocs'],
argTypes: {
domains: {
control: {
type: 'object',
},
},
open: {
control: {
type: 'boolean',
},
},
handleOpen: {
action: 'open',
},
domain: {
control: {
type: 'object',
},
},
branches: {
control: {
type: 'object',
},
},
onUpdate: {
action: 'update',
},
},
};
export default meta;
type Story = StoryObj<typeof EditDomainDialog>;
export const Default: Story = {};
@@ -0,0 +1,29 @@
import { Meta, StoryObj } from '@storybook/react';
import WebhookCard from 'components/projects/project/settings/WebhookCard';
const meta: Meta<typeof WebhookCard> = {
title: 'Project/Settings/WebhookCard',
component: WebhookCard,
tags: ['autodocs'],
argTypes: {
webhookUrl: {
control: {
type: 'text',
},
},
onDelete: {
action: 'delete',
},
},
};
export default meta;
type Story = StoryObj<typeof WebhookCard>;
export const Default: Story = {
args: {
webhookUrl: 'https://api.retool.com',
},
};
-1
View File
@@ -13,7 +13,6 @@ export default defineConfig({
context: '/src/context', context: '/src/context',
components: '/src/components', components: '/src/components',
pages: '/src/pages', pages: '/src/pages',
types: '/src/types',
}, },
}, },
define: { define: {
+12 -1
View File
@@ -185,6 +185,9 @@ type RemoveEnvironmentVariableResponse = {
type UpdateDeploymentToProdResponse = { type UpdateDeploymentToProdResponse = {
updateDeploymentToProd: boolean; updateDeploymentToProd: boolean;
}; };
type AddProjectFromTemplateResponse = {
addProjectFromTemplate: Project;
};
type AddProjectResponse = { type AddProjectResponse = {
addProject: Project; addProject: Project;
}; };
@@ -200,6 +203,13 @@ type DeleteProjectResponse = {
type DeleteDomainResponse = { type DeleteDomainResponse = {
deleteDomain: boolean; deleteDomain: boolean;
}; };
type AddProjectFromTemplateInput = {
templateOwner: string;
templateRepo: string;
owner: string;
name: string;
isPrivate: boolean;
};
type AddProjectInput = { type AddProjectInput = {
name: string; name: string;
repository: string; repository: string;
@@ -267,6 +277,7 @@ declare class GQLClient {
updateEnvironmentVariable(environmentVariableId: string, data: UpdateEnvironmentVariableInput): Promise<UpdateEnvironmentVariableResponse>; updateEnvironmentVariable(environmentVariableId: string, data: UpdateEnvironmentVariableInput): Promise<UpdateEnvironmentVariableResponse>;
removeEnvironmentVariable(environmentVariableId: string): Promise<RemoveEnvironmentVariableResponse>; removeEnvironmentVariable(environmentVariableId: string): Promise<RemoveEnvironmentVariableResponse>;
updateDeploymentToProd(deploymentId: string): Promise<UpdateDeploymentToProdResponse>; updateDeploymentToProd(deploymentId: string): Promise<UpdateDeploymentToProdResponse>;
addProjectFromTemplate(organizationSlug: string, data: AddProjectFromTemplateInput): Promise<AddProjectFromTemplateResponse>;
addProject(organizationSlug: string, data: AddProjectInput): Promise<AddProjectResponse>; addProject(organizationSlug: string, data: AddProjectInput): Promise<AddProjectResponse>;
updateProject(projectId: string, data: UpdateProjectInput): Promise<UpdateProjectResponse>; updateProject(projectId: string, data: UpdateProjectInput): Promise<UpdateProjectResponse>;
updateDomain(domainId: string, data: UpdateDomainInput): Promise<UpdateDomainResponse>; updateDomain(domainId: string, data: UpdateDomainInput): Promise<UpdateDomainResponse>;
@@ -281,4 +292,4 @@ declare class GQLClient {
unauthenticateGithub(): Promise<UnauthenticateGitHubResponse>; unauthenticateGithub(): Promise<UnauthenticateGitHubResponse>;
} }
export { type AddDomainInput, type AddDomainResponse, type AddEnvironmentVariableInput, type AddEnvironmentVariablesResponse, type AddProjectInput, type AddProjectMemberInput, type AddProjectMemberResponse, type AddProjectResponse, type AuthenticateGitHubResponse, type DeleteDeploymentResponse, type DeleteDomainResponse, type DeleteProjectResponse, type Deployment, DeploymentStatus, type Domain, DomainStatus, Environment, type EnvironmentVariable, type FilterDomainInput, GQLClient, type GetDeploymentsResponse, type GetDomainsResponse, type GetEnvironmentVariablesResponse, type GetOrganizationsResponse, type GetProjectMembersResponse, type GetProjectResponse, type GetProjectsInOrganizationResponse, type GetUserResponse, type GraphQLConfig, type Organization, type OrganizationMember, type OrganizationProject, Permission, type Project, type ProjectMember, type RedeployToProdResponse, type RemoveEnvironmentVariableResponse, type RemoveProjectMemberResponse, Role, type RollbackDeploymentResponse, type SearchProjectsResponse, type UnauthenticateGitHubResponse, type UpdateDeploymentToProdResponse, type UpdateDomainInput, type UpdateDomainResponse, type UpdateEnvironmentVariableInput, type UpdateEnvironmentVariableResponse, type UpdateProjectInput, type UpdateProjectMemberInput, type UpdateProjectMemberResponse, type UpdateProjectResponse, type User }; export { type AddDomainInput, type AddDomainResponse, type AddEnvironmentVariableInput, type AddEnvironmentVariablesResponse, type AddProjectFromTemplateInput, type AddProjectFromTemplateResponse, type AddProjectInput, type AddProjectMemberInput, type AddProjectMemberResponse, type AddProjectResponse, type AuthenticateGitHubResponse, type DeleteDeploymentResponse, type DeleteDomainResponse, type DeleteProjectResponse, type Deployment, DeploymentStatus, type Domain, DomainStatus, Environment, type EnvironmentVariable, type FilterDomainInput, GQLClient, type GetDeploymentsResponse, type GetDomainsResponse, type GetEnvironmentVariablesResponse, type GetOrganizationsResponse, type GetProjectMembersResponse, type GetProjectResponse, type GetProjectsInOrganizationResponse, type GetUserResponse, type GraphQLConfig, type Organization, type OrganizationMember, type OrganizationProject, Permission, type Project, type ProjectMember, type RedeployToProdResponse, type RemoveEnvironmentVariableResponse, type RemoveProjectMemberResponse, Role, type RollbackDeploymentResponse, type SearchProjectsResponse, type UnauthenticateGitHubResponse, type UpdateDeploymentToProdResponse, type UpdateDomainInput, type UpdateDomainResponse, type UpdateEnvironmentVariableInput, type UpdateEnvironmentVariableResponse, type UpdateProjectInput, type UpdateProjectMemberInput, type UpdateProjectMemberResponse, type UpdateProjectResponse, type User };
+12 -1
View File
@@ -185,6 +185,9 @@ type RemoveEnvironmentVariableResponse = {
type UpdateDeploymentToProdResponse = { type UpdateDeploymentToProdResponse = {
updateDeploymentToProd: boolean; updateDeploymentToProd: boolean;
}; };
type AddProjectFromTemplateResponse = {
addProjectFromTemplate: Project;
};
type AddProjectResponse = { type AddProjectResponse = {
addProject: Project; addProject: Project;
}; };
@@ -200,6 +203,13 @@ type DeleteProjectResponse = {
type DeleteDomainResponse = { type DeleteDomainResponse = {
deleteDomain: boolean; deleteDomain: boolean;
}; };
type AddProjectFromTemplateInput = {
templateOwner: string;
templateRepo: string;
owner: string;
name: string;
isPrivate: boolean;
};
type AddProjectInput = { type AddProjectInput = {
name: string; name: string;
repository: string; repository: string;
@@ -267,6 +277,7 @@ declare class GQLClient {
updateEnvironmentVariable(environmentVariableId: string, data: UpdateEnvironmentVariableInput): Promise<UpdateEnvironmentVariableResponse>; updateEnvironmentVariable(environmentVariableId: string, data: UpdateEnvironmentVariableInput): Promise<UpdateEnvironmentVariableResponse>;
removeEnvironmentVariable(environmentVariableId: string): Promise<RemoveEnvironmentVariableResponse>; removeEnvironmentVariable(environmentVariableId: string): Promise<RemoveEnvironmentVariableResponse>;
updateDeploymentToProd(deploymentId: string): Promise<UpdateDeploymentToProdResponse>; updateDeploymentToProd(deploymentId: string): Promise<UpdateDeploymentToProdResponse>;
addProjectFromTemplate(organizationSlug: string, data: AddProjectFromTemplateInput): Promise<AddProjectFromTemplateResponse>;
addProject(organizationSlug: string, data: AddProjectInput): Promise<AddProjectResponse>; addProject(organizationSlug: string, data: AddProjectInput): Promise<AddProjectResponse>;
updateProject(projectId: string, data: UpdateProjectInput): Promise<UpdateProjectResponse>; updateProject(projectId: string, data: UpdateProjectInput): Promise<UpdateProjectResponse>;
updateDomain(domainId: string, data: UpdateDomainInput): Promise<UpdateDomainResponse>; updateDomain(domainId: string, data: UpdateDomainInput): Promise<UpdateDomainResponse>;
@@ -281,4 +292,4 @@ declare class GQLClient {
unauthenticateGithub(): Promise<UnauthenticateGitHubResponse>; unauthenticateGithub(): Promise<UnauthenticateGitHubResponse>;
} }
export { type AddDomainInput, type AddDomainResponse, type AddEnvironmentVariableInput, type AddEnvironmentVariablesResponse, type AddProjectInput, type AddProjectMemberInput, type AddProjectMemberResponse, type AddProjectResponse, type AuthenticateGitHubResponse, type DeleteDeploymentResponse, type DeleteDomainResponse, type DeleteProjectResponse, type Deployment, DeploymentStatus, type Domain, DomainStatus, Environment, type EnvironmentVariable, type FilterDomainInput, GQLClient, type GetDeploymentsResponse, type GetDomainsResponse, type GetEnvironmentVariablesResponse, type GetOrganizationsResponse, type GetProjectMembersResponse, type GetProjectResponse, type GetProjectsInOrganizationResponse, type GetUserResponse, type GraphQLConfig, type Organization, type OrganizationMember, type OrganizationProject, Permission, type Project, type ProjectMember, type RedeployToProdResponse, type RemoveEnvironmentVariableResponse, type RemoveProjectMemberResponse, Role, type RollbackDeploymentResponse, type SearchProjectsResponse, type UnauthenticateGitHubResponse, type UpdateDeploymentToProdResponse, type UpdateDomainInput, type UpdateDomainResponse, type UpdateEnvironmentVariableInput, type UpdateEnvironmentVariableResponse, type UpdateProjectInput, type UpdateProjectMemberInput, type UpdateProjectMemberResponse, type UpdateProjectResponse, type User }; export { type AddDomainInput, type AddDomainResponse, type AddEnvironmentVariableInput, type AddEnvironmentVariablesResponse, type AddProjectFromTemplateInput, type AddProjectFromTemplateResponse, type AddProjectInput, type AddProjectMemberInput, type AddProjectMemberResponse, type AddProjectResponse, type AuthenticateGitHubResponse, type DeleteDeploymentResponse, type DeleteDomainResponse, type DeleteProjectResponse, type Deployment, DeploymentStatus, type Domain, DomainStatus, Environment, type EnvironmentVariable, type FilterDomainInput, GQLClient, type GetDeploymentsResponse, type GetDomainsResponse, type GetEnvironmentVariablesResponse, type GetOrganizationsResponse, type GetProjectMembersResponse, type GetProjectResponse, type GetProjectsInOrganizationResponse, type GetUserResponse, type GraphQLConfig, type Organization, type OrganizationMember, type OrganizationProject, Permission, type Project, type ProjectMember, type RedeployToProdResponse, type RemoveEnvironmentVariableResponse, type RemoveProjectMemberResponse, Role, type RollbackDeploymentResponse, type SearchProjectsResponse, type UnauthenticateGitHubResponse, type UpdateDeploymentToProdResponse, type UpdateDomainInput, type UpdateDomainResponse, type UpdateEnvironmentVariableInput, type UpdateEnvironmentVariableResponse, type UpdateProjectInput, type UpdateProjectMemberInput, type UpdateProjectMemberResponse, type UpdateProjectResponse, type User };
+87 -56
View File
@@ -267,93 +267,112 @@ query ($projectId: String!, $filter: FilterDomainsInput) {
// src/mutations.ts // src/mutations.ts
var import_client2 = require("@apollo/client"); var import_client2 = require("@apollo/client");
var removeProjectMember = import_client2.gql` var removeProjectMember = import_client2.gql`
mutation ($projectMemberId: String!) { mutation ($projectMemberId: String!) {
removeProjectMember(projectMemberId: $projectMemberId) removeProjectMember(projectMemberId: $projectMemberId)
} }
`; `;
var updateProjectMember = import_client2.gql` var updateProjectMember = import_client2.gql`
mutation ($projectMemberId: String!, $data: UpdateProjectMemberInput) { mutation ($projectMemberId: String!, $data: UpdateProjectMemberInput) {
updateProjectMember(projectMemberId: $projectMemberId, data: $data) updateProjectMember(projectMemberId: $projectMemberId, data: $data)
} }
`; `;
var addProjectMember = import_client2.gql` var addProjectMember = import_client2.gql`
mutation ($projectId: String!, $data: AddProjectMemberInput) { mutation ($projectId: String!, $data: AddProjectMemberInput) {
addProjectMember(projectId: $projectId, data: $data) addProjectMember(projectId: $projectId, data: $data)
} }
`; `;
var addEnvironmentVariables = import_client2.gql` var addEnvironmentVariables = import_client2.gql`
mutation ($projectId: String!, $data: [AddEnvironmentVariableInput!]) { mutation ($projectId: String!, $data: [AddEnvironmentVariableInput!]) {
addEnvironmentVariables(projectId: $projectId, data: $data) addEnvironmentVariables(projectId: $projectId, data: $data)
} }
`; `;
var updateEnvironmentVariable = import_client2.gql` var updateEnvironmentVariable = import_client2.gql`
mutation ($environmentVariableId: String!, $data: UpdateEnvironmentVariableInput!) { mutation (
updateEnvironmentVariable(environmentVariableId: $environmentVariableId, data: $data) $environmentVariableId: String!
} $data: UpdateEnvironmentVariableInput!
) {
updateEnvironmentVariable(
environmentVariableId: $environmentVariableId
data: $data
)
}
`; `;
var removeEnvironmentVariable = import_client2.gql` var removeEnvironmentVariable = import_client2.gql`
mutation ($environmentVariableId: String!) { mutation ($environmentVariableId: String!) {
removeEnvironmentVariable(environmentVariableId: $environmentVariableId) removeEnvironmentVariable(environmentVariableId: $environmentVariableId)
} }
`; `;
var updateDeploymentToProd = import_client2.gql` var updateDeploymentToProd = import_client2.gql`
mutation ($deploymentId: String!) { mutation ($deploymentId: String!) {
updateDeploymentToProd(deploymentId: $deploymentId) updateDeploymentToProd(deploymentId: $deploymentId)
} }
`;
var addProjectFromTemplate = import_client2.gql`
mutation ($organizationSlug: String!, $data: AddProjectFromTemplateInput) {
addProjectFromTemplate(organizationSlug: $organizationSlug, data: $data) {
id
}
}
`; `;
var addProject = import_client2.gql` var addProject = import_client2.gql`
mutation ($organizationSlug: String!, $data: AddProjectInput) { mutation ($organizationSlug: String!, $data: AddProjectInput) {
addProject(organizationSlug: $organizationSlug, data: $data) { addProject(organizationSlug: $organizationSlug, data: $data) {
id id
}
} }
}`; `;
var updateProjectMutation = import_client2.gql` var updateProjectMutation = import_client2.gql`
mutation ($projectId: String!, $data: UpdateProjectInput) { mutation ($projectId: String!, $data: UpdateProjectInput) {
updateProject(projectId: $projectId, data: $data) updateProject(projectId: $projectId, data: $data)
}`; }
`;
var updateDomainMutation = import_client2.gql` var updateDomainMutation = import_client2.gql`
mutation ($domainId: String!, $data: UpdateDomainInput!) { mutation ($domainId: String!, $data: UpdateDomainInput!) {
updateDomain(domainId: $domainId, data: $data) updateDomain(domainId: $domainId, data: $data)
}`; }
`;
var redeployToProd = import_client2.gql` var redeployToProd = import_client2.gql`
mutation ($deploymentId: String!) { mutation ($deploymentId: String!) {
redeployToProd(deploymentId: $deploymentId) redeployToProd(deploymentId: $deploymentId)
} }
`; `;
var deleteProject = import_client2.gql` var deleteProject = import_client2.gql`
mutation ($projectId: String!) { mutation ($projectId: String!) {
deleteProject(projectId: $projectId) deleteProject(projectId: $projectId)
} }
`; `;
var deleteDomain = import_client2.gql` var deleteDomain = import_client2.gql`
mutation ($domainId: String!) { mutation ($domainId: String!) {
deleteDomain(domainId: $domainId) deleteDomain(domainId: $domainId)
}`; }
`;
var rollbackDeployment = import_client2.gql` var rollbackDeployment = import_client2.gql`
mutation ($projectId: String! ,$deploymentId: String!) { mutation ($projectId: String!, $deploymentId: String!) {
rollbackDeployment(projectId: $projectId, deploymentId: $deploymentId) rollbackDeployment(projectId: $projectId, deploymentId: $deploymentId)
} }
`; `;
var deleteDeployment = import_client2.gql` var deleteDeployment = import_client2.gql`
mutation ($deploymentId: String!) { mutation ($deploymentId: String!) {
deleteDeployment(deploymentId: $deploymentId) deleteDeployment(deploymentId: $deploymentId)
} }
`; `;
var addDomain = import_client2.gql` var addDomain = import_client2.gql`
mutation ($projectId: String!, $data: AddDomainInput!) { mutation ($projectId: String!, $data: AddDomainInput!) {
addDomain(projectId: $projectId, data: $data) addDomain(projectId: $projectId, data: $data)
} }
`; `;
var authenticateGitHub = import_client2.gql` var authenticateGitHub = import_client2.gql`
mutation ($code: String!) { mutation ($code: String!) {
authenticateGitHub(code: $code) { authenticateGitHub(code: $code) {
token token
}
} }
}`; `;
var unauthenticateGitHub = import_client2.gql` var unauthenticateGitHub = import_client2.gql`
mutation { mutation {
unauthenticateGitHub unauthenticateGitHub
}`; }
`;
// src/client.ts // src/client.ts
var defaultOptions = { var defaultOptions = {
@@ -538,6 +557,18 @@ var GQLClient = class {
return data; return data;
}); });
} }
addProjectFromTemplate(organizationSlug, data) {
return __async(this, null, function* () {
const result = yield this.client.mutate({
mutation: addProjectFromTemplate,
variables: {
organizationSlug,
data
}
});
return result.data;
});
}
addProject(organizationSlug, data) { addProject(organizationSlug, data) {
return __async(this, null, function* () { return __async(this, null, function* () {
const result = yield this.client.mutate({ const result = yield this.client.mutate({
File diff suppressed because one or more lines are too long
+87 -56
View File
@@ -240,93 +240,112 @@ query ($projectId: String!, $filter: FilterDomainsInput) {
// src/mutations.ts // src/mutations.ts
import { gql as gql2 } from "@apollo/client"; import { gql as gql2 } from "@apollo/client";
var removeProjectMember = gql2` var removeProjectMember = gql2`
mutation ($projectMemberId: String!) { mutation ($projectMemberId: String!) {
removeProjectMember(projectMemberId: $projectMemberId) removeProjectMember(projectMemberId: $projectMemberId)
} }
`; `;
var updateProjectMember = gql2` var updateProjectMember = gql2`
mutation ($projectMemberId: String!, $data: UpdateProjectMemberInput) { mutation ($projectMemberId: String!, $data: UpdateProjectMemberInput) {
updateProjectMember(projectMemberId: $projectMemberId, data: $data) updateProjectMember(projectMemberId: $projectMemberId, data: $data)
} }
`; `;
var addProjectMember = gql2` var addProjectMember = gql2`
mutation ($projectId: String!, $data: AddProjectMemberInput) { mutation ($projectId: String!, $data: AddProjectMemberInput) {
addProjectMember(projectId: $projectId, data: $data) addProjectMember(projectId: $projectId, data: $data)
} }
`; `;
var addEnvironmentVariables = gql2` var addEnvironmentVariables = gql2`
mutation ($projectId: String!, $data: [AddEnvironmentVariableInput!]) { mutation ($projectId: String!, $data: [AddEnvironmentVariableInput!]) {
addEnvironmentVariables(projectId: $projectId, data: $data) addEnvironmentVariables(projectId: $projectId, data: $data)
} }
`; `;
var updateEnvironmentVariable = gql2` var updateEnvironmentVariable = gql2`
mutation ($environmentVariableId: String!, $data: UpdateEnvironmentVariableInput!) { mutation (
updateEnvironmentVariable(environmentVariableId: $environmentVariableId, data: $data) $environmentVariableId: String!
} $data: UpdateEnvironmentVariableInput!
) {
updateEnvironmentVariable(
environmentVariableId: $environmentVariableId
data: $data
)
}
`; `;
var removeEnvironmentVariable = gql2` var removeEnvironmentVariable = gql2`
mutation ($environmentVariableId: String!) { mutation ($environmentVariableId: String!) {
removeEnvironmentVariable(environmentVariableId: $environmentVariableId) removeEnvironmentVariable(environmentVariableId: $environmentVariableId)
} }
`; `;
var updateDeploymentToProd = gql2` var updateDeploymentToProd = gql2`
mutation ($deploymentId: String!) { mutation ($deploymentId: String!) {
updateDeploymentToProd(deploymentId: $deploymentId) updateDeploymentToProd(deploymentId: $deploymentId)
} }
`;
var addProjectFromTemplate = gql2`
mutation ($organizationSlug: String!, $data: AddProjectFromTemplateInput) {
addProjectFromTemplate(organizationSlug: $organizationSlug, data: $data) {
id
}
}
`; `;
var addProject = gql2` var addProject = gql2`
mutation ($organizationSlug: String!, $data: AddProjectInput) { mutation ($organizationSlug: String!, $data: AddProjectInput) {
addProject(organizationSlug: $organizationSlug, data: $data) { addProject(organizationSlug: $organizationSlug, data: $data) {
id id
}
} }
}`; `;
var updateProjectMutation = gql2` var updateProjectMutation = gql2`
mutation ($projectId: String!, $data: UpdateProjectInput) { mutation ($projectId: String!, $data: UpdateProjectInput) {
updateProject(projectId: $projectId, data: $data) updateProject(projectId: $projectId, data: $data)
}`; }
`;
var updateDomainMutation = gql2` var updateDomainMutation = gql2`
mutation ($domainId: String!, $data: UpdateDomainInput!) { mutation ($domainId: String!, $data: UpdateDomainInput!) {
updateDomain(domainId: $domainId, data: $data) updateDomain(domainId: $domainId, data: $data)
}`; }
`;
var redeployToProd = gql2` var redeployToProd = gql2`
mutation ($deploymentId: String!) { mutation ($deploymentId: String!) {
redeployToProd(deploymentId: $deploymentId) redeployToProd(deploymentId: $deploymentId)
} }
`; `;
var deleteProject = gql2` var deleteProject = gql2`
mutation ($projectId: String!) { mutation ($projectId: String!) {
deleteProject(projectId: $projectId) deleteProject(projectId: $projectId)
} }
`; `;
var deleteDomain = gql2` var deleteDomain = gql2`
mutation ($domainId: String!) { mutation ($domainId: String!) {
deleteDomain(domainId: $domainId) deleteDomain(domainId: $domainId)
}`; }
`;
var rollbackDeployment = gql2` var rollbackDeployment = gql2`
mutation ($projectId: String! ,$deploymentId: String!) { mutation ($projectId: String!, $deploymentId: String!) {
rollbackDeployment(projectId: $projectId, deploymentId: $deploymentId) rollbackDeployment(projectId: $projectId, deploymentId: $deploymentId)
} }
`; `;
var deleteDeployment = gql2` var deleteDeployment = gql2`
mutation ($deploymentId: String!) { mutation ($deploymentId: String!) {
deleteDeployment(deploymentId: $deploymentId) deleteDeployment(deploymentId: $deploymentId)
} }
`; `;
var addDomain = gql2` var addDomain = gql2`
mutation ($projectId: String!, $data: AddDomainInput!) { mutation ($projectId: String!, $data: AddDomainInput!) {
addDomain(projectId: $projectId, data: $data) addDomain(projectId: $projectId, data: $data)
} }
`; `;
var authenticateGitHub = gql2` var authenticateGitHub = gql2`
mutation ($code: String!) { mutation ($code: String!) {
authenticateGitHub(code: $code) { authenticateGitHub(code: $code) {
token token
}
} }
}`; `;
var unauthenticateGitHub = gql2` var unauthenticateGitHub = gql2`
mutation { mutation {
unauthenticateGitHub unauthenticateGitHub
}`; }
`;
// src/client.ts // src/client.ts
var defaultOptions = { var defaultOptions = {
@@ -511,6 +530,18 @@ var GQLClient = class {
return data; return data;
}); });
} }
addProjectFromTemplate(organizationSlug, data) {
return __async(this, null, function* () {
const result = yield this.client.mutate({
mutation: addProjectFromTemplate,
variables: {
organizationSlug,
data
}
});
return result.data;
});
}
addProject(organizationSlug, data) { addProject(organizationSlug, data) {
return __async(this, null, function* () { return __async(this, null, function* () {
const result = yield this.client.mutate({ const result = yield this.client.mutate({
File diff suppressed because one or more lines are too long
+15
View File
@@ -228,6 +228,21 @@ export class GQLClient {
return data; return data;
} }
async addProjectFromTemplate(
organizationSlug: string,
data: types.AddProjectFromTemplateInput
): Promise<types.AddProjectFromTemplateResponse> {
const result = await this.client.mutate({
mutation: mutations.addProjectFromTemplate,
variables: {
organizationSlug,
data,
},
});
return result.data;
}
async addProject( async addProject(
organizationSlug: string, organizationSlug: string,
data: types.AddProjectInput data: types.AddProjectInput
+77 -57
View File
@@ -1,107 +1,127 @@
import { gql } from '@apollo/client'; import { gql } from "@apollo/client";
export const removeProjectMember = gql` export const removeProjectMember = gql`
mutation ($projectMemberId: String!) { mutation ($projectMemberId: String!) {
removeProjectMember(projectMemberId: $projectMemberId) removeProjectMember(projectMemberId: $projectMemberId)
} }
`; `;
export const updateProjectMember = gql` export const updateProjectMember = gql`
mutation ($projectMemberId: String!, $data: UpdateProjectMemberInput) { mutation ($projectMemberId: String!, $data: UpdateProjectMemberInput) {
updateProjectMember(projectMemberId: $projectMemberId, data: $data) updateProjectMember(projectMemberId: $projectMemberId, data: $data)
} }
`; `;
export const addProjectMember = gql` export const addProjectMember = gql`
mutation ($projectId: String!, $data: AddProjectMemberInput) { mutation ($projectId: String!, $data: AddProjectMemberInput) {
addProjectMember(projectId: $projectId, data: $data) addProjectMember(projectId: $projectId, data: $data)
} }
`; `;
export const addEnvironmentVariables = gql` export const addEnvironmentVariables = gql`
mutation ($projectId: String!, $data: [AddEnvironmentVariableInput!]) { mutation ($projectId: String!, $data: [AddEnvironmentVariableInput!]) {
addEnvironmentVariables(projectId: $projectId, data: $data) addEnvironmentVariables(projectId: $projectId, data: $data)
} }
`; `;
export const updateEnvironmentVariable = gql` export const updateEnvironmentVariable = gql`
mutation ($environmentVariableId: String!, $data: UpdateEnvironmentVariableInput!) { mutation (
updateEnvironmentVariable(environmentVariableId: $environmentVariableId, data: $data) $environmentVariableId: String!
} $data: UpdateEnvironmentVariableInput!
) {
updateEnvironmentVariable(
environmentVariableId: $environmentVariableId
data: $data
)
}
`; `;
export const removeEnvironmentVariable = gql` export const removeEnvironmentVariable = gql`
mutation ($environmentVariableId: String!) { mutation ($environmentVariableId: String!) {
removeEnvironmentVariable(environmentVariableId: $environmentVariableId) removeEnvironmentVariable(environmentVariableId: $environmentVariableId)
} }
`; `;
export const updateDeploymentToProd = gql` export const updateDeploymentToProd = gql`
mutation ($deploymentId: String!) { mutation ($deploymentId: String!) {
updateDeploymentToProd(deploymentId: $deploymentId) updateDeploymentToProd(deploymentId: $deploymentId)
} }
`;
export const addProjectFromTemplate = gql`
mutation ($organizationSlug: String!, $data: AddProjectFromTemplateInput) {
addProjectFromTemplate(organizationSlug: $organizationSlug, data: $data) {
id
}
}
`; `;
export const addProject = gql` export const addProject = gql`
mutation ($organizationSlug: String!, $data: AddProjectInput) { mutation ($organizationSlug: String!, $data: AddProjectInput) {
addProject(organizationSlug: $organizationSlug, data: $data) { addProject(organizationSlug: $organizationSlug, data: $data) {
id id
}
} }
}`; `;
export const updateProjectMutation = gql` export const updateProjectMutation = gql`
mutation ($projectId: String!, $data: UpdateProjectInput) { mutation ($projectId: String!, $data: UpdateProjectInput) {
updateProject(projectId: $projectId, data: $data) updateProject(projectId: $projectId, data: $data)
}`; }
`;
export const updateDomainMutation = gql` export const updateDomainMutation = gql`
mutation ($domainId: String!, $data: UpdateDomainInput!) { mutation ($domainId: String!, $data: UpdateDomainInput!) {
updateDomain(domainId: $domainId, data: $data) updateDomain(domainId: $domainId, data: $data)
}`; }
`;
export const redeployToProd = gql` export const redeployToProd = gql`
mutation ($deploymentId: String!) { mutation ($deploymentId: String!) {
redeployToProd(deploymentId: $deploymentId) redeployToProd(deploymentId: $deploymentId)
} }
`; `;
export const deleteProject = gql` export const deleteProject = gql`
mutation ($projectId: String!) { mutation ($projectId: String!) {
deleteProject(projectId: $projectId) deleteProject(projectId: $projectId)
} }
`; `;
export const deleteDomain = gql` export const deleteDomain = gql`
mutation ($domainId: String!) { mutation ($domainId: String!) {
deleteDomain(domainId: $domainId) deleteDomain(domainId: $domainId)
}`; }
`;
export const rollbackDeployment = gql` export const rollbackDeployment = gql`
mutation ($projectId: String! ,$deploymentId: String!) { mutation ($projectId: String!, $deploymentId: String!) {
rollbackDeployment(projectId: $projectId, deploymentId: $deploymentId) rollbackDeployment(projectId: $projectId, deploymentId: $deploymentId)
} }
`; `;
export const deleteDeployment = gql` export const deleteDeployment = gql`
mutation ($deploymentId: String!) { mutation ($deploymentId: String!) {
deleteDeployment(deploymentId: $deploymentId) deleteDeployment(deploymentId: $deploymentId)
} }
`; `;
export const addDomain = gql` export const addDomain = gql`
mutation ($projectId: String!, $data: AddDomainInput!) { mutation ($projectId: String!, $data: AddDomainInput!) {
addDomain(projectId: $projectId, data: $data) addDomain(projectId: $projectId, data: $data)
} }
`; `;
export const authenticateGitHub = gql` export const authenticateGitHub = gql`
mutation ($code: String!) { mutation ($code: String!) {
authenticateGitHub(code: $code) { authenticateGitHub(code: $code) {
token token
}
} }
}`; `;
export const unauthenticateGitHub = gql` export const unauthenticateGitHub = gql`
mutation { mutation {
unauthenticateGitHub unauthenticateGitHub
}`; }
`;
+180 -167
View File
@@ -1,298 +1,311 @@
import { addProjectFromTemplate } from "./mutations";
// Note: equivalent to types present in GQL schema // Note: equivalent to types present in GQL schema
export enum Role { export enum Role {
Owner = 'Owner', Owner = "Owner",
Maintainer = 'Maintainer', Maintainer = "Maintainer",
Reader = 'Reader', Reader = "Reader",
} }
export enum Permission { export enum Permission {
View = 'View', View = "View",
Edit = 'Edit', Edit = "Edit",
} }
export enum Environment { export enum Environment {
Production = 'Production', Production = "Production",
Preview = 'Preview', Preview = "Preview",
Development = 'Development', Development = "Development",
} }
export enum DeploymentStatus { export enum DeploymentStatus {
Building = 'Building', Building = "Building",
Ready = 'Ready', Ready = "Ready",
Error = 'Error', Error = "Error",
Deleting = 'Deleting' Deleting = "Deleting",
} }
export enum DomainStatus { export enum DomainStatus {
Live = 'Live', Live = "Live",
Pending = 'Pending', Pending = "Pending",
} }
export type EnvironmentVariable = { export type EnvironmentVariable = {
id: string id: string;
environment: Environment environment: Environment;
key: string key: string;
value: string value: string;
createdAt: string createdAt: string;
updatedAt: string updatedAt: string;
} };
export type Domain = { export type Domain = {
id: string id: string;
branch: string branch: string;
name: string name: string;
status: DomainStatus status: DomainStatus;
redirectTo: Domain | null redirectTo: Domain | null;
createdAt: string createdAt: string;
updatedAt: string updatedAt: string;
} };
export type User = { export type User = {
id: string id: string;
name: string | null name: string | null;
email: string email: string;
isVerified: boolean isVerified: boolean;
createdAt: string createdAt: string;
updatedAt: string updatedAt: string;
gitHubToken: string | null gitHubToken: string | null;
} };
export type Deployment = { export type Deployment = {
id: string id: string;
domain: Domain domain: Domain;
branch: string branch: string;
commitHash: string commitHash: string;
commitMessage: string commitMessage: string;
url?: string url?: string;
environment: Environment environment: Environment;
isCurrent: boolean isCurrent: boolean;
status: DeploymentStatus status: DeploymentStatus;
createdBy: User createdBy: User;
createdAt: string createdAt: string;
updatedAt: string updatedAt: string;
} };
export type OrganizationMember = { export type OrganizationMember = {
id: string id: string;
member: User member: User;
role: Role role: Role;
createdAt: string createdAt: string;
updatedAt: string updatedAt: string;
} };
export type ProjectMember = { export type ProjectMember = {
id: string id: string;
member: User member: User;
permissions: Permission[] permissions: Permission[];
isPending: boolean isPending: boolean;
createdAt: string createdAt: string;
updatedAt: string updatedAt: string;
} };
export type OrganizationProject = { export type OrganizationProject = {
id: string id: string;
owner: User owner: User;
deployments: Deployment[] deployments: Deployment[];
name: string name: string;
repository: string repository: string;
prodBranch: string prodBranch: string;
description: string description: string;
template: string template: string;
framework: string framework: string;
webhooks: string[] webhooks: string[];
members: ProjectMember[] members: ProjectMember[];
environmentVariables: EnvironmentVariable[] environmentVariables: EnvironmentVariable[];
createdAt: string createdAt: string;
updatedAt: string updatedAt: string;
} };
export type Organization = { export type Organization = {
id: string id: string;
name: string name: string;
slug: string slug: string;
projects: OrganizationProject[] projects: OrganizationProject[];
createdAt: string createdAt: string;
updatedAt: string updatedAt: string;
members: OrganizationMember[] members: OrganizationMember[];
} };
export type Project = { export type Project = {
id: string id: string;
owner: User owner: User;
deployments: Deployment[] deployments: Deployment[];
name: string name: string;
repository: string repository: string;
prodBranch: string prodBranch: string;
description: string description: string;
template: string template: string;
framework: string framework: string;
webhooks: string[] webhooks: string[];
members: ProjectMember[] members: ProjectMember[];
environmentVariables: EnvironmentVariable[] environmentVariables: EnvironmentVariable[];
createdAt: string createdAt: string;
updatedAt: string updatedAt: string;
organization: Organization organization: Organization;
icon: string icon: string;
subDomain: string subDomain: string;
} };
export type GetProjectMembersResponse = { export type GetProjectMembersResponse = {
projectMembers: ProjectMember[] projectMembers: ProjectMember[];
} };
export type AddProjectMemberResponse = { export type AddProjectMemberResponse = {
addProjectMember: boolean addProjectMember: boolean;
} };
export type RemoveProjectMemberResponse = { export type RemoveProjectMemberResponse = {
removeProjectMember: boolean; removeProjectMember: boolean;
} };
export type UpdateProjectMemberResponse = { export type UpdateProjectMemberResponse = {
updateProjectMember: boolean; updateProjectMember: boolean;
} };
export type GetDeploymentsResponse = { export type GetDeploymentsResponse = {
deployments: Deployment[] deployments: Deployment[];
} };
export type GetEnvironmentVariablesResponse = { export type GetEnvironmentVariablesResponse = {
environmentVariables: EnvironmentVariable[] environmentVariables: EnvironmentVariable[];
} };
export type GetOrganizationsResponse = { export type GetOrganizationsResponse = {
organizations: Organization[] organizations: Organization[];
} };
export type GetUserResponse = { export type GetUserResponse = {
user: User user: User;
} };
export type GetProjectResponse = { export type GetProjectResponse = {
project: Project | null project: Project | null;
} };
export type GetProjectsInOrganizationResponse = { export type GetProjectsInOrganizationResponse = {
projectsInOrganization: Project[] projectsInOrganization: Project[];
} };
export type GetDomainsResponse = { export type GetDomainsResponse = {
domains: Domain[] domains: Domain[];
} };
export type SearchProjectsResponse = { export type SearchProjectsResponse = {
searchProjects: Project[] searchProjects: Project[];
} };
export type AddEnvironmentVariablesResponse = { export type AddEnvironmentVariablesResponse = {
addEnvironmentVariables: boolean; addEnvironmentVariables: boolean;
} };
export type AddEnvironmentVariableInput = { export type AddEnvironmentVariableInput = {
environments: string[]; environments: string[];
key: string; key: string;
value: string; value: string;
} };
export type UpdateEnvironmentVariableInput = { export type UpdateEnvironmentVariableInput = {
key: string; key: string;
value: string; value: string;
} };
export type UpdateProjectMemberInput = { export type UpdateProjectMemberInput = {
permissions: Permission[]; permissions: Permission[];
} };
export type AddProjectMemberInput = { export type AddProjectMemberInput = {
email: string; email: string;
permissions: Permission[] permissions: Permission[];
} };
export type UpdateEnvironmentVariableResponse = { export type UpdateEnvironmentVariableResponse = {
updateEnvironmentVariable: boolean; updateEnvironmentVariable: boolean;
} };
export type RemoveEnvironmentVariableResponse = { export type RemoveEnvironmentVariableResponse = {
removeEnvironmentVariable: boolean; removeEnvironmentVariable: boolean;
} };
export type UpdateDeploymentToProdResponse = { export type UpdateDeploymentToProdResponse = {
updateDeploymentToProd: boolean; updateDeploymentToProd: boolean;
} };
export type AddProjectFromTemplateResponse = {
addProjectFromTemplate: Project;
};
export type AddProjectResponse = { export type AddProjectResponse = {
addProject: Project addProject: Project;
} };
export type UpdateProjectResponse = { export type UpdateProjectResponse = {
updateProject: boolean; updateProject: boolean;
} };
export type UpdateDomainResponse = { export type UpdateDomainResponse = {
updateDomain: boolean; updateDomain: boolean;
} };
export type DeleteProjectResponse = { export type DeleteProjectResponse = {
deleteProject: boolean; deleteProject: boolean;
} };
export type DeleteDomainResponse = { export type DeleteDomainResponse = {
deleteDomain: boolean; deleteDomain: boolean;
} };
export type AddProjectFromTemplateInput = {
templateOwner: string;
templateRepo: string;
owner: string;
name: string;
isPrivate: boolean;
};
export type AddProjectInput = { export type AddProjectInput = {
name: string; name: string;
repository: string; repository: string;
prodBranch: string; prodBranch: string;
template?: string; template?: string;
} };
export type UpdateProjectInput = { export type UpdateProjectInput = {
name?: string name?: string;
description?: string description?: string;
prodBranch?: string prodBranch?: string;
webhooks?: string[] webhooks?: string[];
organizationId?: string organizationId?: string;
} };
export type UpdateDomainInput = { export type UpdateDomainInput = {
name?: string; name?: string;
branch?: string; branch?: string;
redirectToId?: string | null; redirectToId?: string | null;
} };
export type RedeployToProdResponse = { export type RedeployToProdResponse = {
redeployToProd: boolean redeployToProd: boolean;
} };
export type RollbackDeploymentResponse = { export type RollbackDeploymentResponse = {
rollbackDeployment: boolean rollbackDeployment: boolean;
} };
export type DeleteDeploymentResponse = { export type DeleteDeploymentResponse = {
deleteDeployment: boolean deleteDeployment: boolean;
} };
export type AddDomainInput = { export type AddDomainInput = {
name: string name: string;
} };
export type FilterDomainInput = { export type FilterDomainInput = {
branch?: string branch?: string;
status?: DomainStatus status?: DomainStatus;
} };
export type AddDomainResponse = { export type AddDomainResponse = {
addDomain: true addDomain: true;
} };
export type AuthenticateGitHubResponse = { export type AuthenticateGitHubResponse = {
authenticateGitHub: { authenticateGitHub: {
token: string token: string;
} };
} };
export type UnauthenticateGitHubResponse = { export type UnauthenticateGitHubResponse = {
unauthenticateGitHub: boolean unauthenticateGitHub: boolean;
} };
-12
View File
@@ -11264,11 +11264,6 @@ globby@11.1.0, globby@^11.0.1, globby@^11.0.2, globby@^11.0.3, globby@^11.1.0:
merge2 "^1.4.1" merge2 "^1.4.1"
slash "^3.0.0" slash "^3.0.0"
goober@^2.1.10:
version "2.1.14"
resolved "https://registry.yarnpkg.com/goober/-/goober-2.1.14.tgz#4a5c94fc34dc086a8e6035360ae1800005135acd"
integrity sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==
google-protobuf@^3.19.4: google-protobuf@^3.19.4:
version "3.21.2" version "3.21.2"
resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.21.2.tgz#4580a2bea8bbb291ee579d1fefb14d6fa3070ea4" resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.21.2.tgz#4580a2bea8bbb291ee579d1fefb14d6fa3070ea4"
@@ -15328,13 +15323,6 @@ react-hook-form@^7.49.0:
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.51.3.tgz#7486dd2d52280b6b28048c099a98d2545931cab3" resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.51.3.tgz#7486dd2d52280b6b28048c099a98d2545931cab3"
integrity sha512-cvJ/wbHdhYx8aviSWh28w9ImjmVsb5Y05n1+FW786vEZQJV5STNM0pW6ujS+oiBecb0ARBxJFyAnXj9+GHXACQ== integrity sha512-cvJ/wbHdhYx8aviSWh28w9ImjmVsb5Y05n1+FW786vEZQJV5STNM0pW6ujS+oiBecb0ARBxJFyAnXj9+GHXACQ==
react-hot-toast@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/react-hot-toast/-/react-hot-toast-2.4.1.tgz#df04295eda8a7b12c4f968e54a61c8d36f4c0994"
integrity sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==
dependencies:
goober "^2.1.10"
react-inspector@6.0.2: react-inspector@6.0.2:
version "6.0.2" version "6.0.2"
resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-6.0.2.tgz#aa3028803550cb6dbd7344816d5c80bf39d07e9d" resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-6.0.2.tgz#aa3028803550cb6dbd7344816d5c80bf39d07e9d"