Skip to content

Commit

Permalink
feat(ui): new create connection page (#3072)
Browse files Browse the repository at this point in the history
## 😘 Changes

Fixes https://linear.app/nango/issue/NAN-2188/test-connection-ui

- UI: New create connection page
~The goal is to give the opportunity for customers to change the
`EndUser` (also education time) and display links to documentation. It's
one more click but yeah.~

- UI: Keep the legacy page to a new endpoint for a moment
- UI: add new style for Button, Input, Select
https://www.figma.com/design/pMnSC7rhSM79Dqwhm7MFhd

- `POST /api/v1/connect/sessions` now accepts the same payload as the
public one
- `GET /api/v1/meta` expose user UUID 
~Not sure what the use of id but it's handy to have static-random id for
the endUser profile without exposing our auto-incremented number.~
unused in the end

## 🧪 Tests

- Go to UI > Connections > Create
- Also go to Integrations > any > Add connection

![Screenshot 2024-12-02 at 13 17
52](https://github.com/user-attachments/assets/c2f9b096-1ecc-4deb-ad44-69fca96af931)
  • Loading branch information
bodinsamuel authored Dec 4, 2024
1 parent e238a7b commit 7b0a7c2
Show file tree
Hide file tree
Showing 21 changed files with 1,978 additions and 1,457 deletions.
198 changes: 198 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/server/lib/controllers/connect/postSessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as endUserService from '@nangohq/shared';
import * as connectSessionService from '../../services/connectSession.service.js';
import { requireEmptyQuery, zodErrorToHTTP } from '@nangohq/utils';

const bodySchema = z
export const bodySchema = z
.object({
end_user: z
.object({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { asyncWrapper } from '../../../../utils/asyncWrapper.js';
import { requireEmptyQuery, zodErrorToHTTP } from '@nangohq/utils';
import type { PostConnectSessions, PostInternalConnectSessions } from '@nangohq/types';
import { postConnectSessions } from '../../../connect/postSessions.js';
import { bodySchema as originalBodySchema, postConnectSessions } from '../../../connect/postSessions.js';
import { z } from 'zod';

const bodySchema = z
.object({
allowed_integrations: z.array(z.string()).optional()
allowed_integrations: originalBodySchema.shape.allowed_integrations,
end_user: originalBodySchema.shape.end_user,
organization: originalBodySchema.shape.organization
})
.strict();

Expand All @@ -23,15 +25,14 @@ export const postInternalConnectSessions = asyncWrapper<PostInternalConnectSessi
return;
}

const { user } = res.locals;
const body: PostInternalConnectSessions['Body'] = valBody.data;

// req.body is never but we want to fake it on purpose
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
req.body = {
allowed_integrations: body.allowed_integrations,
// @ts-expect-error body does not accept end_user but we still want to set it
end_user: { id: `nango_dashboard_${user.id}`, email: user.email, display_name: user.name }
end_user: body.end_user,
organization: body.organization
} satisfies PostConnectSessions['Body'];

// @ts-expect-error on internal api we pass ?env= but it's not allowed in public api
Expand Down
2 changes: 1 addition & 1 deletion packages/types/lib/connect/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,5 @@ export type PostInternalConnectSessions = Endpoint<{
Method: 'POST';
Path: '/api/v1/connect/sessions';
Success: PostConnectSessions['Success'];
Body: Pick<ConnectSessionPayload, 'allowed_integrations'>;
Body: Pick<ConnectSessionPayload, 'allowed_integrations' | 'end_user' | 'organization'>;
}>;
11 changes: 10 additions & 1 deletion packages/types/lib/integration/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ export type DeletePublicIntegration = Endpoint<{
Success: { success: true };
}>;

export type ApiIntegration = Omit<Merge<IntegrationConfig, ApiTimestamps>, 'oauth_client_secret_iv' | 'oauth_client_secret_tag'>;

export type GetIntegrations = Endpoint<{
Method: 'GET';
Path: '/api/v1/integrations';
Success: {
data: ApiIntegration[];
};
}>;

export type PostIntegration = Endpoint<{
Method: 'POST';
Path: '/api/v1/integrations';
Expand All @@ -62,7 +72,6 @@ export type PostIntegration = Endpoint<{
};
}>;

export type ApiIntegration = Omit<Merge<IntegrationConfig, ApiTimestamps>, 'oauth_client_secret_iv' | 'oauth_client_secret_tag'>;
export type GetIntegration = Endpoint<{
Method: 'GET';
Path: '/api/v1/integrations/:providerConfigKey';
Expand Down
1 change: 1 addition & 0 deletions packages/webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@nangohq/server": "file:../server",
"@nangohq/types": "file:../types",
"@radix-ui/react-avatar": "1.1.1",
"@radix-ui/react-collapsible": "1.1.1",
"@radix-ui/react-dialog": "1.1.1",
"@radix-ui/react-dropdown-menu": "2.1.1",
"@radix-ui/react-hover-card": "1.1.1",
Expand Down
5 changes: 3 additions & 2 deletions packages/webapp/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import CreateIntegration from './pages/Integrations/Create';
import { ShowIntegration } from './pages/Integrations/providerConfigKey/Show';
import { ConnectionList } from './pages/Connection/List';
import { ConnectionShow } from './pages/Connection/Show';
import ConnectionCreate from './pages/Connection/Create';
import { ConnectionCreate } from './pages/Connection/Create';
import { EnvironmentSettings } from './pages/Environment/Settings';
import { PrivateRoute } from './components/PrivateRoute';
import ForgotPassword from './pages/Account/ForgotPassword';
Expand All @@ -35,6 +35,7 @@ import { TeamSettings } from './pages/Team/Settings';
import { UserSettings } from './pages/User/Settings';
import { Root } from './pages/Root';
import { globalEnv } from './utils/env';
import { ConnectionCreateLegacy } from './pages/Connection/CreateLegacy';
import { Helmet } from 'react-helmet';

const theme = createTheme({
Expand Down Expand Up @@ -95,7 +96,7 @@ const App = () => {
<Route path="/:env/integrations/:providerConfigKey/*" element={<ShowIntegration />} />
<Route path="/:env/connections" element={<ConnectionList />} />
<Route path="/:env/connections/create" element={<ConnectionCreate />} />
<Route path="/:env/connections/create/:providerConfigKey" element={<ConnectionCreate />} />
<Route path="/:env/connections/create-legacy" element={<ConnectionCreateLegacy />} />
<Route path="/:env/connections/:providerConfigKey/:connectionId" element={<ConnectionShow />} />
<Route path="/:env/activity" element={<Navigate to={`/${env}/logs`} replace={true} />} />
<Route path="/:env/logs" element={<LogsSearch />} />
Expand Down
9 changes: 4 additions & 5 deletions packages/webapp/src/components/Info.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import type React from 'react';
import { Alert, AlertDescription, AlertTitle } from './ui/Alert';
import { InfoCircledIcon } from '@radix-ui/react-icons';
import type { ComponentProps } from 'react';
import { cn } from '../utils/utils';
import Button from './ui/button/Button';
import { IconX } from '@tabler/icons-react';
import { IconInfoCircleFilled, IconX } from '@tabler/icons-react';

export const Info: React.FC<{ children: React.ReactNode; icon?: React.ReactNode; title?: string; onClose?: () => void } & ComponentProps<typeof Alert>> = ({
children,
Expand All @@ -18,17 +17,17 @@ export const Info: React.FC<{ children: React.ReactNode; icon?: React.ReactNode;
{icon ?? (
<div
className={cn(
'rounded-full p-1',
'rounded-full p-0.5',
props.variant === 'destructive' && 'bg-red-base-35',
props.variant === 'warning' && 'bg-yellow-base-35',
(!props.variant || props.variant === 'default') && 'bg-blue-base-35'
)}
>
<InfoCircledIcon className="h-4 w-4" />
<IconInfoCircleFilled className="h-3.5 w-3.5" />
</div>
)}
<div className="w-full flex items-center justify-between">
<div>
<div className="flex flex-col gap-2">
{title && <AlertTitle>{title}</AlertTitle>}
<AlertDescription>{children}</AlertDescription>
</div>
Expand Down
9 changes: 7 additions & 2 deletions packages/webapp/src/components/SimpleTooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import type React from 'react';
import { Tooltip, TooltipProvider, TooltipContent, TooltipTrigger } from './ui/Tooltip';
import type { Content } from '@radix-ui/react-tooltip';

export const SimpleTooltip: React.FC<React.PropsWithChildren<{ tooltipContent: React.ReactNode }>> = ({ tooltipContent, children }) => {
export const SimpleTooltip: React.FC<React.PropsWithChildren<{ tooltipContent: React.ReactNode } & React.ComponentPropsWithoutRef<typeof Content>>> = ({
tooltipContent,
children,
...rest
}) => {
if (!tooltipContent) {
return <>{children}</>;
}

return (
<TooltipProvider>
<Tooltip>
<TooltipContent>{tooltipContent}</TooltipContent>
<TooltipContent {...rest}>{tooltipContent}</TooltipContent>
<TooltipTrigger>{children}</TooltipTrigger>
</Tooltip>
</TooltipProvider>
Expand Down
Loading

0 comments on commit 7b0a7c2

Please sign in to comment.