Team switcher
This commit is contained in:
@ -3,7 +3,7 @@ import * as React from 'react';
|
|||||||
import styled, { withTheme } from 'styled-components';
|
import styled, { withTheme } from 'styled-components';
|
||||||
import { ExpandedIcon } from 'outline-icons';
|
import { ExpandedIcon } from 'outline-icons';
|
||||||
import Flex from 'shared/components/Flex';
|
import Flex from 'shared/components/Flex';
|
||||||
import TeamLogo from './TeamLogo';
|
import TeamLogo from 'shared/components/TeamLogo';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
teamName: string,
|
teamName: string,
|
||||||
|
@ -20,7 +20,7 @@ router.use('/', google.routes());
|
|||||||
router.get('/redirect', auth(), async ctx => {
|
router.get('/redirect', auth(), async ctx => {
|
||||||
const user = ctx.state.user;
|
const user = ctx.state.user;
|
||||||
|
|
||||||
// transfer cookie from root to subdomain specific
|
// transfer access token cookie from root to subdomain
|
||||||
ctx.cookies.set('accessToken', undefined, {
|
ctx.cookies.set('accessToken', undefined, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
domain: stripSubdomain(ctx.request.hostname),
|
domain: stripSubdomain(ctx.request.hostname),
|
||||||
|
@ -118,6 +118,7 @@ export default function auth(options?: { required?: boolean } = {}) {
|
|||||||
[team.subdomain]: {
|
[team.subdomain]: {
|
||||||
name: team.name,
|
name: team.name,
|
||||||
logo: team.logo,
|
logo: team.logo,
|
||||||
|
url: team.url,
|
||||||
expires,
|
expires,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -15,9 +15,10 @@ export const screenshotUrl = `${process.env.URL}/screenshot.png`;
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children?: React.Node,
|
children?: React.Node,
|
||||||
|
sessions?: Object,
|
||||||
};
|
};
|
||||||
|
|
||||||
function Layout({ children }: Props) {
|
function Layout({ children, sessions }: Props) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
@ -65,7 +66,7 @@ function Layout({ children }: Props) {
|
|||||||
{'{{CSS}}'}
|
{'{{CSS}}'}
|
||||||
</head>
|
</head>
|
||||||
<Body>
|
<Body>
|
||||||
<TopNavigation />
|
<TopNavigation sessions={sessions} />
|
||||||
{children}
|
{children}
|
||||||
<BottomNavigation />
|
<BottomNavigation />
|
||||||
</Body>
|
</Body>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { sortBy } from 'lodash';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import breakpoint from 'styled-components-breakpoint';
|
import breakpoint from 'styled-components-breakpoint';
|
||||||
import Centered from './Centered';
|
import Centered from './Centered';
|
||||||
|
import TeamLogo from '../../../shared/components/TeamLogo';
|
||||||
import {
|
import {
|
||||||
developers,
|
developers,
|
||||||
changelog,
|
changelog,
|
||||||
@ -13,7 +15,18 @@ import {
|
|||||||
spectrumUrl,
|
spectrumUrl,
|
||||||
} from '../../../shared/utils/routeHelpers';
|
} from '../../../shared/utils/routeHelpers';
|
||||||
|
|
||||||
function TopNavigation() {
|
type Sessions = {
|
||||||
|
[subdomain: string]: {
|
||||||
|
name: string,
|
||||||
|
logo: string,
|
||||||
|
expires: string,
|
||||||
|
url: string,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function TopNavigation({ sessions }: { sessions: ?Sessions }) {
|
||||||
|
const orderedSessions = sortBy(sessions, 'name');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Nav>
|
<Nav>
|
||||||
<Brand href={process.env.URL}>Outline</Brand>
|
<Brand href={process.env.URL}>Outline</Brand>
|
||||||
@ -33,9 +46,25 @@ function TopNavigation() {
|
|||||||
<MenuItem>
|
<MenuItem>
|
||||||
<a href={developers()}>API</a>
|
<a href={developers()}>API</a>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem>
|
{orderedSessions.length ? (
|
||||||
<a href="/#signin">Sign In</a>
|
<MenuItem highlighted>
|
||||||
</MenuItem>
|
<a href={developers()}>Your Teams</a>
|
||||||
|
<ol>
|
||||||
|
{orderedSessions.map(session => (
|
||||||
|
<MenuItem key={session.url}>
|
||||||
|
<a href={`${session.url}/dashboard`}>
|
||||||
|
<TeamLogo src={session.logo} width={20} height={20} />
|
||||||
|
{session.name}
|
||||||
|
</a>
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
</MenuItem>
|
||||||
|
) : (
|
||||||
|
<MenuItem>
|
||||||
|
<a href="/#signin">Sign In</a>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
</Menu>
|
</Menu>
|
||||||
</Nav>
|
</Nav>
|
||||||
);
|
);
|
||||||
@ -74,13 +103,8 @@ const MenuLinkStyle = props => `
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Menu = styled.ul`
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
list-style: none;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const MenuItem = styled.li`
|
const MenuItem = styled.li`
|
||||||
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0 0 0 40px;
|
margin: 0 0 0 40px;
|
||||||
|
|
||||||
@ -89,6 +113,33 @@ const MenuItem = styled.li`
|
|||||||
}
|
}
|
||||||
|
|
||||||
${MenuLinkStyle};
|
${MenuLinkStyle};
|
||||||
|
|
||||||
|
${props =>
|
||||||
|
props.highlighted &&
|
||||||
|
`
|
||||||
|
position: relative;
|
||||||
|
border: 2px solid ${props.theme.slate};
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
margin-top: -6px;
|
||||||
|
margin-bottom: -6px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: 2px solid ${props.theme.slateDark};
|
||||||
|
|
||||||
|
> a {
|
||||||
|
color: ${props.theme.slateDark};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> a:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
`};
|
||||||
|
|
||||||
|
&:hover ol {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const MenuItemDesktop = styled(MenuItem)`
|
const MenuItemDesktop = styled(MenuItem)`
|
||||||
@ -99,6 +150,42 @@ const MenuItemDesktop = styled(MenuItem)`
|
|||||||
`};
|
`};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const Menu = styled.ul`
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
ol {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 32px;
|
||||||
|
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
min-width: 160px;
|
||||||
|
padding: 0 0.5em;
|
||||||
|
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05), 0 4px 8px rgba(0, 0, 0, 0.08),
|
||||||
|
0 2px 4px rgba(0, 0, 0, 0.08);
|
||||||
|
|
||||||
|
${MenuItem} {
|
||||||
|
padding: 0.5em 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
${MenuItem} a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
${TeamLogo} {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
const Nav = styled(Centered)`
|
const Nav = styled(Centered)`
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
|
@ -66,21 +66,12 @@ router.get('/changelog', async ctx => {
|
|||||||
// home page
|
// home page
|
||||||
router.get('/', async ctx => {
|
router.get('/', async ctx => {
|
||||||
const lastSignedIn = ctx.cookies.get('lastSignedIn');
|
const lastSignedIn = ctx.cookies.get('lastSignedIn');
|
||||||
const sessions = JSON.parse(ctx.cookies.get('sessions') || '{}');
|
|
||||||
const domain = parseDomain(ctx.request.hostname);
|
const domain = parseDomain(ctx.request.hostname);
|
||||||
const subdomain = domain ? domain.subdomain : undefined;
|
const subdomain = domain ? domain.subdomain : undefined;
|
||||||
console.log('domain', domain);
|
const accessToken = ctx.cookies.get('accessToken');
|
||||||
console.log('subdomain', subdomain);
|
|
||||||
|
|
||||||
let accessToken;
|
|
||||||
if (subdomain) {
|
|
||||||
accessToken = sessions[subdomain] && sessions[subdomain].accessToken;
|
|
||||||
} else {
|
|
||||||
accessToken = sessions.root
|
|
||||||
? sessions.root.accessToken
|
|
||||||
: ctx.cookies.get('accessToken');
|
|
||||||
}
|
|
||||||
console.log('accessToken', accessToken);
|
console.log('accessToken', accessToken);
|
||||||
|
ctx.set('Cache-Control', 'no-cache');
|
||||||
|
|
||||||
if (accessToken) {
|
if (accessToken) {
|
||||||
return renderapp(ctx);
|
return renderapp(ctx);
|
||||||
|
@ -13,10 +13,11 @@ import theme from '../../shared/styles/theme';
|
|||||||
const sheet = new ServerStyleSheet();
|
const sheet = new ServerStyleSheet();
|
||||||
|
|
||||||
export default function renderpage(ctx: Object, children: React.Node) {
|
export default function renderpage(ctx: Object, children: React.Node) {
|
||||||
|
const sessions = JSON.parse(ctx.cookies.get('sessions') || '{}');
|
||||||
const html = ReactDOMServer.renderToString(
|
const html = ReactDOMServer.renderToString(
|
||||||
<StyleSheetManager sheet={sheet.instance}>
|
<StyleSheetManager sheet={sheet.instance}>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<Layout>{children}</Layout>
|
<Layout sessions={sessions}>{children}</Layout>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</StyleSheetManager>
|
</StyleSheetManager>
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user