mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-22 19:41:53 +03:00
Generate Token through Auth0
This commit is contained in:
parent
54acb16db8
commit
8e0dc44bf6
3
.gitignore
vendored
3
.gitignore
vendored
@ -1 +1,2 @@
|
||||
.vscode/*
|
||||
.vscode/*
|
||||
**/**/.env
|
97
README.md
97
README.md
@ -1,40 +1,87 @@
|
||||
# twenty
|
||||
# Twenty
|
||||
|
||||
Welcome to Twenty!
|
||||
Welcome to Twenty documentation!
|
||||
|
||||
## Setup & Development
|
||||
## High Level Overview
|
||||
|
||||
Twenty development stack is composed of 5 different layers:
|
||||
- twenty-front: our frontend React app
|
||||
- twenty-api (Hasura): our backend presentation layer that can do straight forward CRUDs, permissionning, authentication.
|
||||
- twenty-server: our backend that contain complex logics, scripts, jobs...
|
||||
- [tbd] twenty-events (Jitsu): our event ingestor which is separated from api and server to ensure high availability
|
||||
- storages: postgres, [tbd] elasticsearch, [tbd] redis.
|
||||
|
||||
## Development environment setup
|
||||
|
||||
This section only discusses the development setup. The whole developemnt environment is containerized with Docker and orchestrated with docker-compose.
|
||||
|
||||
### Step 1: pre-requesites
|
||||
Make sure to have the latest Docker and Docker-compose versions installed on your computer.
|
||||
|
||||
### Step 2: docker build
|
||||
Build docker containers.
|
||||
|
||||
The whole setup/development experience is happening in `infra/dev` folder. Make sure to be in this folder:
|
||||
```
|
||||
docker-compose -f infra/dev/docker-compose.yml up --build --force-recreate
|
||||
cd infra/dev
|
||||
```
|
||||
|
||||
Browse:
|
||||
- FE/BE: localhost:3000
|
||||
- Hasura: localhost:8080
|
||||
|
||||
## Tests
|
||||
|
||||
Ssh into the twenty-server container using:
|
||||
- `docker ps` to get the container id
|
||||
- `docker exec -it CONTAINER_ID sh`
|
||||
|
||||
### Frontend
|
||||
|
||||
```
|
||||
cd front
|
||||
npm run test
|
||||
docker-compose up --build --force-recreate
|
||||
```
|
||||
|
||||
### Backend
|
||||
Once this is completed you should have:
|
||||
- twenty-front available on: http://localhost:3001
|
||||
- twenty-api available on: http://localhost:8080
|
||||
- twenty-server available on: http://localhost:3000/health
|
||||
- postgres: available on http://localhost:5432 that should contain two database: twenty (data) and hasura (metadata)
|
||||
|
||||
### Step 3: environment file
|
||||
Configure your environment by copying the `.env.example` file located in `infra/dev` folder into `.env`.
|
||||
```
|
||||
cd server
|
||||
npm run test
|
||||
cp infra/dev/.env.example infra/dev/.env
|
||||
```
|
||||
|
||||
## Production
|
||||
Then, you'll need to replace all REPLACE_ME variable by their development value. Please reach out to another engineer to get these values (as most of them are third party credentials, sensitive data)
|
||||
|
||||
### Step 4: API (Hasura) metadata
|
||||
Browse Hasura console on http://localhost:8080, go to settings and import metadata file located in `infra/dev/twenty-api` folder
|
||||
|
||||
## Developping on Frontend
|
||||
|
||||
The development FE server is running on docker up and is exposing the `twenty-front` on port http://localhost:3001. As you modify the `/front` folder on your computer, this folder is synced with your `twenty-front` container and the frontend application is automatically refreshed.
|
||||
|
||||
### Develop
|
||||
|
||||
Recommended: as you modify frontend code, here is how to access `twenty-front` server logs in order to debug / watch typescript issues:
|
||||
```
|
||||
cd front && npm run build
|
||||
cd ../server && npm run build
|
||||
```
|
||||
docker-compose up
|
||||
docker-compose logs twenty-front -f
|
||||
```
|
||||
|
||||
### Open a shell into the container
|
||||
```
|
||||
docker-compose exec twenty-front sh
|
||||
```
|
||||
|
||||
### Tests
|
||||
|
||||
#### Unit tests:
|
||||
```
|
||||
docker-compose exec twenty-front sh -c "npm run test"
|
||||
# coverage
|
||||
docker-compose exec twenty-front sh -c "npm run coverage"
|
||||
```
|
||||
|
||||
#### Storybook:
|
||||
```
|
||||
docker-compose exec twenty-front sh -c "npm run storybook"
|
||||
```
|
||||
|
||||
## Developping on API
|
||||
|
||||
The API is a Hasura instance which is a no-code container. To modify API behavior, you'll need to connect to Hasura console on: http://localhost:8080/console
|
||||
|
||||
## Developping on server
|
||||
|
||||
Section TBD
|
5350
front/package-lock.json
generated
5350
front/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -3,6 +3,8 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.7.5",
|
||||
"@auth0/auth0-react": "^2.0.0",
|
||||
"@emotion/react": "^11.10.5",
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.2.1",
|
||||
@ -12,6 +14,7 @@
|
||||
"@types/node": "^16.18.4",
|
||||
"@types/react": "^18.0.25",
|
||||
"@types/react-dom": "^18.0.9",
|
||||
"graphql": "^16.6.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.4.4",
|
||||
@ -19,7 +22,7 @@
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"start": "cp ../infra/dev/.env ./.env && PORT=3001 react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
@ -46,8 +49,7 @@
|
||||
"overrides": {
|
||||
"react-refresh": "0.14.0"
|
||||
},
|
||||
"jest": {
|
||||
},
|
||||
"jest": {},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
|
@ -2,16 +2,61 @@ import React from 'react';
|
||||
import Inbox from './pages/inbox/Inbox';
|
||||
import Contacts from './pages/Contacts';
|
||||
import Insights from './pages/Insights';
|
||||
import AuthCallback from './pages/AuthCallback';
|
||||
import AppLayout from './layout/AppLayout';
|
||||
import RequireAuth from './components/RequireAuth';
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
import { useQuery, gql } from '@apollo/client';
|
||||
|
||||
const GET_USER_PROFILE = gql`
|
||||
query GetUserProfile {
|
||||
users {
|
||||
id
|
||||
email
|
||||
first_name
|
||||
last_name
|
||||
tenant {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
function App() {
|
||||
const { data } = useQuery(GET_USER_PROFILE, {
|
||||
fetchPolicy: 'network-only',
|
||||
});
|
||||
const user = data?.users[0];
|
||||
|
||||
return (
|
||||
<AppLayout>
|
||||
<AppLayout user={user}>
|
||||
<Routes>
|
||||
<Route path="/" element={<Inbox />} />
|
||||
<Route path="/contacts" element={<Contacts />} />
|
||||
<Route path="/insights" element={<Insights />} />
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
<RequireAuth>
|
||||
<Inbox />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/contacts"
|
||||
element={
|
||||
<RequireAuth>
|
||||
<Contacts />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/insights"
|
||||
element={
|
||||
<RequireAuth>
|
||||
<Insights />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route path="/auth/callback" element={<AuthCallback />} />
|
||||
</Routes>
|
||||
</AppLayout>
|
||||
);
|
||||
|
10
front/src/components/RequireAuth.tsx
Normal file
10
front/src/components/RequireAuth.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import AuthService from '../hooks/AuthenticationHooks';
|
||||
|
||||
function RequireAuth({ children }: { children: JSX.Element }): JSX.Element {
|
||||
const { redirectIfNotLoggedIn } = AuthService;
|
||||
redirectIfNotLoggedIn();
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
export default RequireAuth;
|
14
front/src/components/__stories__/RequireAuth.stories.tsx
Normal file
14
front/src/components/__stories__/RequireAuth.stories.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import RequireAuth from '../RequireAuth';
|
||||
|
||||
import Navbar from '../RequireAuth';
|
||||
|
||||
export default {
|
||||
title: 'RequireAuth',
|
||||
component: Navbar,
|
||||
};
|
||||
|
||||
export const RequireAuthWithHelloChild = () => (
|
||||
<RequireAuth>
|
||||
<div>Hello</div>
|
||||
</RequireAuth>
|
||||
);
|
9
front/src/components/__tests__/RequireAuth.test.tsx
Normal file
9
front/src/components/__tests__/RequireAuth.test.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { RequireAuthWithHelloChild } from '../__stories__/RequireAuth.stories';
|
||||
|
||||
it('Checks the Require Auth renders', () => {
|
||||
const { getAllByText } = render(<RequireAuthWithHelloChild />);
|
||||
|
||||
expect(getAllByText('Hello')).toBeTruthy();
|
||||
});
|
39
front/src/hooks/AuthenticationHooks.ts
Normal file
39
front/src/hooks/AuthenticationHooks.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { useAuth0 } from '@auth0/auth0-react';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
const useIsNotLoggedIn = () => {
|
||||
const { isAuthenticated, isLoading } = useAuth0();
|
||||
const hasAccessToken = localStorage.getItem('accessToken');
|
||||
return (!isAuthenticated || !hasAccessToken) && !isLoading;
|
||||
};
|
||||
|
||||
const redirectIfNotLoggedIn = () => {
|
||||
const isNotLoggedIn = useIsNotLoggedIn();
|
||||
const { loginWithRedirect } = useAuth0();
|
||||
if (isNotLoggedIn) {
|
||||
loginWithRedirect();
|
||||
}
|
||||
};
|
||||
|
||||
const useGetAccessToken = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [token, setToken] = useState('');
|
||||
const { getAccessTokenSilently } = useAuth0();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchToken = async () => {
|
||||
setLoading(true);
|
||||
const accessToken = await getAccessTokenSilently();
|
||||
localStorage.setItem('accessToken', accessToken);
|
||||
|
||||
setLoading(false);
|
||||
setToken(accessToken);
|
||||
};
|
||||
|
||||
fetchToken();
|
||||
}, []);
|
||||
|
||||
return { loading, token };
|
||||
};
|
||||
|
||||
export default { useIsNotLoggedIn, useGetAccessToken, redirectIfNotLoggedIn };
|
106
front/src/hooks/__tests__/AuthenticationHooks.test.tsx
Normal file
106
front/src/hooks/__tests__/AuthenticationHooks.test.tsx
Normal file
@ -0,0 +1,106 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import AuthenticationHooks from '../AuthenticationHooks';
|
||||
import { useAuth0 } from '@auth0/auth0-react';
|
||||
import { mocked } from 'jest-mock';
|
||||
|
||||
jest.mock('@auth0/auth0-react');
|
||||
const mockedUseAuth0 = mocked(useAuth0, true);
|
||||
|
||||
const user = {
|
||||
email: 'johndoe@me.com',
|
||||
email_verified: true,
|
||||
sub: 'google-oauth2|12345678901234',
|
||||
};
|
||||
|
||||
describe('useIsNotLoggedIn', () => {
|
||||
beforeEach(() => {
|
||||
window.localStorage.clear();
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
it('returns false if auth0 is loading', () => {
|
||||
mockedUseAuth0.mockReturnValue({
|
||||
isAuthenticated: false,
|
||||
user,
|
||||
logout: jest.fn(),
|
||||
loginWithRedirect: jest.fn(),
|
||||
getAccessTokenWithPopup: jest.fn(),
|
||||
getAccessTokenSilently: jest.fn(),
|
||||
getIdTokenClaims: jest.fn(),
|
||||
loginWithPopup: jest.fn(),
|
||||
handleRedirectCallback: jest.fn(),
|
||||
isLoading: true,
|
||||
});
|
||||
|
||||
const { useIsNotLoggedIn } = AuthenticationHooks;
|
||||
const { result } = renderHook(() => useIsNotLoggedIn());
|
||||
const isNotLoggedIn = result.current;
|
||||
|
||||
expect(isNotLoggedIn).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false if token is not there', () => {
|
||||
mockedUseAuth0.mockReturnValue({
|
||||
isAuthenticated: false,
|
||||
user,
|
||||
logout: jest.fn(),
|
||||
loginWithRedirect: jest.fn(),
|
||||
getAccessTokenWithPopup: jest.fn(),
|
||||
getAccessTokenSilently: jest.fn(),
|
||||
getIdTokenClaims: jest.fn(),
|
||||
loginWithPopup: jest.fn(),
|
||||
handleRedirectCallback: jest.fn(),
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
const { useIsNotLoggedIn } = AuthenticationHooks;
|
||||
const { result } = renderHook(() => useIsNotLoggedIn());
|
||||
const isNotLoggedIn = result.current;
|
||||
|
||||
expect(isNotLoggedIn).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false if token is there but user is not connected on auth0', () => {
|
||||
mockedUseAuth0.mockReturnValue({
|
||||
isAuthenticated: false,
|
||||
user,
|
||||
logout: jest.fn(),
|
||||
loginWithRedirect: jest.fn(),
|
||||
getAccessTokenWithPopup: jest.fn(),
|
||||
getAccessTokenSilently: jest.fn(),
|
||||
getIdTokenClaims: jest.fn(),
|
||||
loginWithPopup: jest.fn(),
|
||||
handleRedirectCallback: jest.fn(),
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
window.localStorage.setItem('accessToken', 'token');
|
||||
const { useIsNotLoggedIn } = AuthenticationHooks;
|
||||
const { result } = renderHook(() => useIsNotLoggedIn());
|
||||
const isNotLoggedIn = result.current;
|
||||
|
||||
expect(isNotLoggedIn).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false if token is there and user is connected on auth0', () => {
|
||||
mockedUseAuth0.mockReturnValue({
|
||||
isAuthenticated: true,
|
||||
user,
|
||||
logout: jest.fn(),
|
||||
loginWithRedirect: jest.fn(),
|
||||
getAccessTokenWithPopup: jest.fn(),
|
||||
getAccessTokenSilently: jest.fn(),
|
||||
getIdTokenClaims: jest.fn(),
|
||||
loginWithPopup: jest.fn(),
|
||||
handleRedirectCallback: jest.fn(),
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
window.localStorage.setItem('accessToken', 'token');
|
||||
const { useIsNotLoggedIn } = AuthenticationHooks;
|
||||
const { result } = renderHook(() => useIsNotLoggedIn());
|
||||
const isNotLoggedIn = result.current;
|
||||
|
||||
expect(isNotLoggedIn).toBe(false);
|
||||
});
|
||||
});
|
@ -3,12 +3,48 @@ import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { Auth0Provider } from '@auth0/auth0-react';
|
||||
import {
|
||||
ApolloClient,
|
||||
InMemoryCache,
|
||||
ApolloProvider,
|
||||
createHttpLink,
|
||||
} from '@apollo/client';
|
||||
import { setContext } from '@apollo/client/link/context';
|
||||
|
||||
const httpLink = createHttpLink({ uri: process.env.REACT_APP_API_URL });
|
||||
|
||||
const authLink = setContext((_, { headers }) => {
|
||||
const token = localStorage.getItem('accessToken');
|
||||
return {
|
||||
headers: {
|
||||
...headers,
|
||||
authorization: token ? `Bearer ${token}` : '',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const client = new ApolloClient({
|
||||
link: authLink.concat(httpLink),
|
||||
cache: new InMemoryCache(),
|
||||
});
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement,
|
||||
);
|
||||
root.render(
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>,
|
||||
<Auth0Provider
|
||||
domain={process.env.REACT_APP_AUTH0_DOMAIN || ''}
|
||||
clientId={process.env.REACT_APP_AUTH0_CLIENT_ID || ''}
|
||||
authorizationParams={{
|
||||
redirect_uri: process.env.REACT_APP_AUTH0_CALLBACK_URL || '',
|
||||
audience: process.env.REACT_APP_AUTH0_AUDIENCE || '',
|
||||
}}
|
||||
>
|
||||
<ApolloProvider client={client}>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</ApolloProvider>
|
||||
</Auth0Provider>,
|
||||
);
|
||||
|
@ -9,12 +9,18 @@ const StyledLayout = styled.div`
|
||||
|
||||
type OwnProps = {
|
||||
children: JSX.Element;
|
||||
user?: {
|
||||
email: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
tenant: { id: string; name: string };
|
||||
};
|
||||
};
|
||||
|
||||
function AppLayout({ children }: OwnProps) {
|
||||
function AppLayout({ children, user }: OwnProps) {
|
||||
return (
|
||||
<StyledLayout>
|
||||
<Navbar />
|
||||
<Navbar user={user} />
|
||||
<div>{children}</div>
|
||||
</StyledLayout>
|
||||
);
|
||||
|
@ -1,50 +1,68 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useMatch, useResolvedPath } from 'react-router-dom';
|
||||
import NavItem from './NavItem';
|
||||
import ProfileContainer from './ProfileContainer';
|
||||
|
||||
const NavbarContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
justify-content: space-between;
|
||||
padding-left: 12px;
|
||||
height: 58px;
|
||||
border-bottom: 2px solid #eaecee;
|
||||
`;
|
||||
|
||||
function Navbar() {
|
||||
const NavItemsContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
user?: {
|
||||
email: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
tenant: { id: string; name: string };
|
||||
};
|
||||
};
|
||||
|
||||
function Navbar({ user }: OwnProps) {
|
||||
return (
|
||||
<>
|
||||
<NavbarContainer>
|
||||
<NavItem
|
||||
label="Inbox"
|
||||
to="/"
|
||||
active={
|
||||
!!useMatch({
|
||||
path: useResolvedPath('/').pathname,
|
||||
end: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<NavItem
|
||||
label="Contacts"
|
||||
to="/contacts"
|
||||
active={
|
||||
!!useMatch({
|
||||
path: useResolvedPath('/contacts').pathname,
|
||||
end: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<NavItem
|
||||
label="Insights"
|
||||
to="/insights"
|
||||
active={
|
||||
!!useMatch({
|
||||
path: useResolvedPath('/insights').pathname,
|
||||
end: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<NavItemsContainer>
|
||||
<NavItem
|
||||
label="Inbox"
|
||||
to="/"
|
||||
active={
|
||||
!!useMatch({
|
||||
path: useResolvedPath('/').pathname,
|
||||
end: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<NavItem
|
||||
label="Contacts"
|
||||
to="/contacts"
|
||||
active={
|
||||
!!useMatch({
|
||||
path: useResolvedPath('/contacts').pathname,
|
||||
end: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<NavItem
|
||||
label="Insights"
|
||||
to="/insights"
|
||||
active={
|
||||
!!useMatch({
|
||||
path: useResolvedPath('/insights').pathname,
|
||||
end: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</NavItemsContainer>
|
||||
<ProfileContainer user={user} />
|
||||
</NavbarContainer>
|
||||
</>
|
||||
);
|
||||
|
74
front/src/layout/navbar/ProfileContainer.tsx
Normal file
74
front/src/layout/navbar/ProfileContainer.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
type OwnProps = {
|
||||
user?: {
|
||||
email: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
tenant: { id: string; name: string };
|
||||
};
|
||||
};
|
||||
|
||||
const StyledContainer = styled.button`
|
||||
display: flex;
|
||||
height: 60px;
|
||||
background: inherit;
|
||||
align-items: center;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
font-size: 14px;
|
||||
margin-bottom: -2px;
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
`;
|
||||
|
||||
const StyledInfoContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const StyledEmail = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const StyledTenant = styled.div`
|
||||
display: flex;
|
||||
text-transform: capitalize;
|
||||
font-weight: bold;
|
||||
`;
|
||||
|
||||
const StyledAvatar = styled.div`
|
||||
display: flex;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 40px;
|
||||
background: black;
|
||||
font-size: 20px;
|
||||
color: white;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
margin-right: 16px;
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
function ProfileContainer({ user }: OwnProps) {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledAvatar>
|
||||
{user?.first_name
|
||||
.split(' ')
|
||||
.map((n) => n[0])
|
||||
.join('')}
|
||||
</StyledAvatar>
|
||||
<StyledInfoContainer>
|
||||
<StyledEmail>{user?.email}</StyledEmail>
|
||||
<StyledTenant>{user?.tenant.name}</StyledTenant>
|
||||
</StyledInfoContainer>
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProfileContainer;
|
@ -9,6 +9,13 @@ export default {
|
||||
|
||||
export const NavbarOnInsights = () => (
|
||||
<MemoryRouter initialEntries={['/insights']}>
|
||||
<Navbar />
|
||||
<Navbar
|
||||
user={{
|
||||
email: 'charles@twenty.com',
|
||||
first_name: 'Charles',
|
||||
last_name: 'Bochet',
|
||||
tenant: { id: '1', name: 'Twenty' },
|
||||
}}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
18
front/src/pages/AuthCallback.tsx
Normal file
18
front/src/pages/AuthCallback.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import AuthService from '../hooks/AuthenticationHooks';
|
||||
|
||||
function AuthCallback() {
|
||||
const { useGetAccessToken } = AuthService;
|
||||
|
||||
const { token } = useGetAccessToken();
|
||||
|
||||
useEffect(() => {
|
||||
if (token) {
|
||||
window.location.href = '/';
|
||||
}
|
||||
}, [token]);
|
||||
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
export default AuthCallback;
|
@ -1,5 +1,3 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
function PluginHistory() {
|
||||
return <div></div>;
|
||||
}
|
||||
|
6
infra/dev/.env.example
Normal file
6
infra/dev/.env.example
Normal file
@ -0,0 +1,6 @@
|
||||
HASURA_GRAPHQL_JWT_SECRET=REPLACE_ME
|
||||
HASURA_GRAPHQL_ADMIN_SECRET=hasura_secret
|
||||
REACT_APP_AUTH0_DOMAIN=twenty-dev.eu.auth0.com
|
||||
REACT_APP_AUTH0_CLIENT_ID=REPLACE_ME
|
||||
REACT_APP_AUTH0_CALLBACK_URL=http://localhost:3001/auth/callback
|
||||
REACT_APP_AUTH0_AUDIENCE=hasura-dev
|
@ -1,5 +1,17 @@
|
||||
version: "3.9"
|
||||
services:
|
||||
twenty-front:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: ./infra/dev/twenty-front/Dockerfile
|
||||
ports:
|
||||
- "3001:3001"
|
||||
- "6006:6006"
|
||||
volumes:
|
||||
- ../../front:/app/front
|
||||
- ../../infra:/app/infra
|
||||
depends_on:
|
||||
- postgres
|
||||
twenty-server:
|
||||
build:
|
||||
context: ../..
|
||||
@ -20,6 +32,8 @@ services:
|
||||
PG_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/twenty
|
||||
HASURA_GRAPHQL_ENABLE_CONSOLE: "true"
|
||||
HASURA_GRAPHQL_DEV_MODE: "true"
|
||||
HASURA_GRAPHQL_ADMIN_SECRET: ${HASURA_GRAPHQL_ADMIN_SECRET}
|
||||
HASURA_GRAPHQL_JWT_SECRET: ${HASURA_GRAPHQL_JWT_SECRET}
|
||||
HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log
|
||||
postgres:
|
||||
build: ./postgres
|
||||
|
10
infra/dev/twenty-front/Dockerfile
Normal file
10
infra/dev/twenty-front/Dockerfile
Normal file
@ -0,0 +1,10 @@
|
||||
FROM node:18-alpine as app
|
||||
|
||||
WORKDIR /app
|
||||
COPY ../.. .
|
||||
|
||||
WORKDIR /app/front
|
||||
RUN npm install
|
||||
RUN npm run build
|
||||
|
||||
CMD ["npm", "run", "start"]
|
@ -3,10 +3,6 @@ FROM node:18-alpine as app
|
||||
WORKDIR /app
|
||||
COPY ../.. .
|
||||
|
||||
WORKDIR /app/front
|
||||
RUN npm install
|
||||
RUN npm run build
|
||||
|
||||
WORKDIR /app/server
|
||||
RUN npm install
|
||||
RUN npm run build
|
||||
|
Loading…
Reference in New Issue
Block a user