diff --git a/public/assets/Ark@1x.svg b/public/assets/Ark@1x.svg index 95e26baf552..2173ac158ae 100644 --- a/public/assets/Ark@1x.svg +++ b/public/assets/Ark@1x.svg @@ -1,3 +1,3 @@ - + diff --git a/public/assets/CSGO2.svg b/public/assets/CSGO2.svg index 7157526a6f0..a138aa57549 100644 --- a/public/assets/CSGO2.svg +++ b/public/assets/CSGO2.svg @@ -1,3 +1,3 @@ - + diff --git a/public/assets/Rust.svg b/public/assets/Rust.svg index 66707193cb3..0caf6063821 100644 --- a/public/assets/Rust.svg +++ b/public/assets/Rust.svg @@ -1,3 +1,17 @@ - - - + + + + Rust + Created with Sketch. + + + + + + + + + + + + \ No newline at end of file diff --git a/public/assets/Terraria.svg b/public/assets/Terraria.svg index 71f0f922c8c..f81c05ee2cb 100644 --- a/public/assets/Terraria.svg +++ b/public/assets/Terraria.svg @@ -1,3 +1,3 @@ - + diff --git a/src/__data__/images.ts b/src/__data__/images.ts index 25cedce0e07..71b8d2cdcd8 100644 --- a/src/__data__/images.ts +++ b/src/__data__/images.ts @@ -252,3 +252,30 @@ export const images: Linode.Image[] = [ description: null } ]; + +export const privateImages: Linode.Image[] = [ + { + created_by: 'linode', + deprecated: false, + id: 'linode/debian8.7', + vendor: 'Debian', + size: 1100, + type: 'manual', + created: '2017-08-15T22:28:13', + is_public: false, + label: 'Debian 8.7', + description: null + }, + { + created_by: 'linode', + deprecated: false, + id: 'linode/containerlinux', + vendor: 'CoreOS', + size: 3000, + type: 'manual', + created: '2017-08-15T22:28:13', + is_public: false, + label: 'Container Linux', + description: null + } +]; diff --git a/src/components/AccessPanel/AccessPanel.tsx b/src/components/AccessPanel/AccessPanel.tsx index a5e6c4c71e8..b53de7baf4f 100644 --- a/src/components/AccessPanel/AccessPanel.tsx +++ b/src/components/AccessPanel/AccessPanel.tsx @@ -1,4 +1,6 @@ +import * as classNames from 'classnames'; import * as React from 'react'; +import { compose } from 'recompose'; import CheckBox from 'src/components/CheckBox'; import Paper from 'src/components/core/Paper'; import { @@ -11,7 +13,7 @@ import TableHead from 'src/components/core/TableHead'; import TableRow from 'src/components/core/TableRow'; import Notice from 'src/components/Notice'; import PasswordInput from 'src/components/PasswordInput'; -import RenderGuard from 'src/components/RenderGuard'; +import RenderGuard, { RenderGuardProps } from 'src/components/RenderGuard'; import Table from 'src/components/Table'; import TableCell from 'src/components/TableCell'; import TableHeader from 'src/components/TableHeader'; @@ -59,11 +61,6 @@ const styles: StyleRulesCallback = theme => ({ const styled = withStyles(styles); -export interface Disabled { - disabled?: boolean; - reason?: string; -} - interface Props { password: string | null; error?: string; @@ -74,7 +71,10 @@ interface Props { required?: boolean; placeholder?: string; users?: UserSSHKeyObject[]; - passwordFieldDisabled?: Disabled; + disabled?: boolean; + disabledReason?: string; + hideStrengthLabel?: boolean; + className?: string; } export interface UserSSHKeyObject { @@ -100,24 +100,33 @@ class AccessPanel extends React.Component { required, placeholder, users, - passwordFieldDisabled + disabled, + disabledReason, + hideStrengthLabel, + className } = this.props; return ( - +
{error && } {users && users.length > 0 && this.renderUserSSHKeyTable(users)}
@@ -175,4 +184,7 @@ class AccessPanel extends React.Component { this.props.handleChange(e.target.value); } -export default styled(RenderGuard(AccessPanel)); +export default compose( + RenderGuard, + styled +)(AccessPanel); diff --git a/src/components/AccessPanel/index.ts b/src/components/AccessPanel/index.ts index 99ac4c91dc6..3a95f2305ad 100644 --- a/src/components/AccessPanel/index.ts +++ b/src/components/AccessPanel/index.ts @@ -1,9 +1,7 @@ import AccessPanel, { - Disabled as _Disabled, UserSSHKeyObject as _UserSSHKeyObject } from './AccessPanel'; /* tslint:disable */ -export interface Disabled extends _Disabled {} export interface UserSSHKeyObject extends _UserSSHKeyObject {} export default AccessPanel; diff --git a/src/components/CheckoutBar/CheckoutBar.tsx b/src/components/CheckoutBar/CheckoutBar.tsx index a2793f7af48..629fc1f5c56 100644 --- a/src/components/CheckoutBar/CheckoutBar.tsx +++ b/src/components/CheckoutBar/CheckoutBar.tsx @@ -1,6 +1,5 @@ import * as classNames from 'classnames'; import * as React from 'react'; -import { StickyProps } from 'react-sticky'; import Button from 'src/components/Button'; import { StyleRulesCallback, @@ -67,13 +66,12 @@ interface Props { onDeploy: () => void; heading: string; calculatedPrice?: number; - isSticky?: boolean; disabled?: boolean; isMakingRequest?: boolean; displaySections?: { title: string; details?: string | number }[]; } -type CombinedProps = Props & StickyProps & WithStyles; +type CombinedProps = Props & WithStyles; class CheckoutBar extends React.Component { static defaultProps: Partial = { @@ -82,13 +80,6 @@ class CheckoutBar extends React.Component { render() { const { - /** - * Note: - * This 'style' prop is what gives us the "sticky" styles. Other special - * props are available, see https://github.com/captivationsoftware/react-sticky - */ - style, - isSticky, classes, onDeploy, heading, @@ -98,16 +89,8 @@ class CheckoutBar extends React.Component { isMakingRequest } = this.props; - let finalStyle; - if (isSticky) { - finalStyle = { - ...style, - paddingTop: 24 - }; - } - return ( -
+
{ } } -export default styled(RenderGuard(InfoPanel)); +export default compose( + RenderGuard, + styled +)(InfoPanel); diff --git a/src/components/PasswordInput/PasswordInput.tsx b/src/components/PasswordInput/PasswordInput.tsx index 77b6095d54c..186b286e286 100644 --- a/src/components/PasswordInput/PasswordInput.tsx +++ b/src/components/PasswordInput/PasswordInput.tsx @@ -16,6 +16,7 @@ type Props = TextFieldProps & { value?: string; required?: boolean; disabledReason?: string; + hideStrengthLabel?: boolean; }; interface State { @@ -67,7 +68,14 @@ class PasswordInput extends React.Component { render() { const { strength } = this.state; - const { classes, value, required, disabledReason, ...rest } = this.props; + const { + classes, + value, + required, + disabledReason, + hideStrengthLabel, + ...rest + } = this.props; return ( @@ -82,11 +90,12 @@ class PasswordInput extends React.Component { required={required} /> - { - - - - } + + + Password must be at least 6 characters and contain each of the diff --git a/src/components/PasswordInput/StrengthIndicator.tsx b/src/components/PasswordInput/StrengthIndicator.tsx index 7661b3ece52..12f5ff898f0 100644 --- a/src/components/PasswordInput/StrengthIndicator.tsx +++ b/src/components/PasswordInput/StrengthIndicator.tsx @@ -11,6 +11,7 @@ import Grid from 'src/components/Grid'; interface Props { strength: null | 0 | 1 | 2 | 3; + hideStrengthLabel?: boolean; } type ClassNames = 'root' | 'block' | 'strengthText' | 'strengthLabel'; @@ -49,7 +50,7 @@ const styled = withStyles(styles); type CombinedProps = Props & WithStyles; const StrengthIndicator: React.StatelessComponent = props => { - const { classes, strength } = props; + const { classes, strength, hideStrengthLabel } = props; return ( = props => { className={classes.strengthText} data-qa-password-strength > - Strength: + {!hideStrengthLabel && ( + Strength: + )} {strength ? (strength === 1 && ' Weak') || (strength === 2 && ' Fair') || diff --git a/src/components/SelectRegionPanel/SelectRegionPanel.tsx b/src/components/SelectRegionPanel/SelectRegionPanel.tsx index 27b2edb4a5c..281aec45ad3 100644 --- a/src/components/SelectRegionPanel/SelectRegionPanel.tsx +++ b/src/components/SelectRegionPanel/SelectRegionPanel.tsx @@ -6,13 +6,14 @@ import SG from 'flag-icon-css/flags/4x3/sg.svg'; import US from 'flag-icon-css/flags/4x3/us.svg'; import { isEmpty } from 'ramda'; import * as React from 'react'; +import { compose } from 'recompose'; import { StyleRulesCallback, withStyles, WithStyles } from 'src/components/core/styles'; import Grid from 'src/components/Grid'; -import RenderGuard from 'src/components/RenderGuard'; +import RenderGuard, { RenderGuardProps } from 'src/components/RenderGuard'; import SelectionCard from 'src/components/SelectionCard'; import TabbedPanel from 'src/components/TabbedPanel'; import { Tab } from 'src/components/TabbedPanel/TabbedPanel'; @@ -50,7 +51,7 @@ interface Props { copy?: string; error?: string; handleSelection: (id: string) => void; - selectedID: string | null; + selectedID?: string; disabled?: boolean; } @@ -64,8 +65,8 @@ const getASRegions = (regions: ExtendedRegion[]) => regions.filter(r => /(jp|sg)/.test(r.country)); const renderCard = ( - selectedID: string | null, handleSelection: Function, + selectedID?: string, disabled?: boolean ) => (region: ExtendedRegion, idx: number) => ( { return ( - {na.map(renderCard(selectedID, handleSelection, disabled))} + {na.map(renderCard(handleSelection, selectedID, disabled))} ); } @@ -108,7 +109,7 @@ class SelectRegionPanel extends React.Component< render: () => { return ( - {eu.map(renderCard(selectedID, handleSelection, disabled))} + {eu.map(renderCard(handleSelection, selectedID, disabled))} ); } @@ -121,7 +122,7 @@ class SelectRegionPanel extends React.Component< render: () => { return ( - {as.map(renderCard(selectedID, handleSelection, disabled))} + {as.map(renderCard(handleSelection, selectedID, disabled))} ); } @@ -149,6 +150,10 @@ class SelectRegionPanel extends React.Component< const styled = withStyles(styles); -export default styled( - RenderGuard>(SelectRegionPanel) -); +export default compose< + Props & WithStyles, + Props & RenderGuardProps +>( + RenderGuard, + styled +)(SelectRegionPanel); diff --git a/src/components/ShowMoreExpansion/ShowMoreExpansion.stories.tsx b/src/components/ShowMoreExpansion/ShowMoreExpansion.stories.tsx index 6bd702106f2..9e7257b7fe7 100644 --- a/src/components/ShowMoreExpansion/ShowMoreExpansion.stories.tsx +++ b/src/components/ShowMoreExpansion/ShowMoreExpansion.stories.tsx @@ -4,7 +4,7 @@ import Typography from 'src/components/core/Typography'; import ShowMoreExpansion from './ShowMoreExpansion'; storiesOf('ShowMoreExpansion', module).add('default', () => ( - + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim diff --git a/src/components/ShowMoreExpansion/ShowMoreExpansion.tsx b/src/components/ShowMoreExpansion/ShowMoreExpansion.tsx index 4c80bfd0036..7980f5a6c66 100644 --- a/src/components/ShowMoreExpansion/ShowMoreExpansion.tsx +++ b/src/components/ShowMoreExpansion/ShowMoreExpansion.tsx @@ -42,18 +42,17 @@ const styles: StyleRulesCallback = theme => ({ interface Props { name: string; + defaultExpanded?: boolean; } interface State { - open: boolean; + open: boolean | undefined; } type CombinedProps = Props & WithStyles; class ShowMoreExpansion extends React.Component { - state = { - open: false - }; + state = { open: this.props.defaultExpanded || false }; handleNameClick = () => { this.setState({ @@ -61,6 +60,15 @@ class ShowMoreExpansion extends React.Component { }); }; + componentDidUpdate(prevProps: Props, prevState: State) { + if ( + prevState.open !== this.props.defaultExpanded && + prevProps.defaultExpanded !== this.props.defaultExpanded + ) { + this.setState({ open: this.props.defaultExpanded }); + } + } + render() { const { name, classes, children } = this.props; const { open } = this.state; diff --git a/src/containers/profile.container.ts b/src/containers/profile.container.ts new file mode 100644 index 00000000000..c690067896d --- /dev/null +++ b/src/containers/profile.container.ts @@ -0,0 +1,15 @@ +import { connect } from 'react-redux'; +import { ApplicationState } from 'src/store'; + +export interface ProfileProps { + profile?: Linode.Profile; +} + +export default ( + mapAccountToProps: (ownProps: TOutter, account?: Linode.Profile) => TInner +) => + connect((state: ApplicationState, ownProps: TOutter) => { + const profile = state.__resources.profile.data; + + return mapAccountToProps(ownProps, profile); + }); diff --git a/src/features/NodeBalancers/NodeBalancerCreate.tsx b/src/features/NodeBalancers/NodeBalancerCreate.tsx index 7cd5046602e..3087d8defb5 100644 --- a/src/features/NodeBalancers/NodeBalancerCreate.tsx +++ b/src/features/NodeBalancers/NodeBalancerCreate.tsx @@ -511,7 +511,7 @@ class NodeBalancerCreate extends React.Component { diff --git a/src/features/StackScripts/SelectStackScriptPanel/SelectStackScriptPanel.tsx b/src/features/StackScripts/SelectStackScriptPanel/SelectStackScriptPanel.tsx index 19cafd56a35..cce14b1cad8 100644 --- a/src/features/StackScripts/SelectStackScriptPanel/SelectStackScriptPanel.tsx +++ b/src/features/StackScripts/SelectStackScriptPanel/SelectStackScriptPanel.tsx @@ -1,24 +1,26 @@ -import { compose, pathOr } from 'ramda'; +import { pathOr } from 'ramda'; import * as React from 'react'; import { connect } from 'react-redux'; +import { compose } from 'recompose'; import Button from 'src/components/Button'; import CircleProgress from 'src/components/CircleProgress'; +import Paper from 'src/components/core/Paper'; import { StyleRulesCallback, withStyles, WithStyles } from 'src/components/core/styles'; import Typography from 'src/components/core/Typography'; -import RenderGuard from 'src/components/RenderGuard'; -import TabbedPanel from 'src/components/TabbedPanel'; +import Notice from 'src/components/Notice'; +import RenderGuard, { RenderGuardProps } from 'src/components/RenderGuard'; import Table from 'src/components/Table'; import { getStackScript } from 'src/services/stackscripts'; import { MapState } from 'src/store/types'; import { formatDate } from 'src/utilities/format-date-iso8601'; +import { getParamFromUrl } from 'src/utilities/queryParams'; import stripImageName from 'src/utilities/stripImageName'; import truncateText from 'src/utilities/truncateText'; import StackScriptTableHead from '../Partials/StackScriptTableHead'; -import { StackScriptTabs } from '../stackScriptUtils'; import SelectStackScriptPanelContent from './SelectStackScriptPanelContent'; import StackScriptSelectionRow from './StackScriptSelectionRow'; @@ -27,7 +29,14 @@ export interface ExtendedLinode extends Linode.Linode { subHeadings: string[]; } -type ClassNames = 'root' | 'table' | 'link' | 'selecting'; +type ClassNames = + | 'root' + | 'table' + | 'link' + | 'selecting' + | 'panel' + | 'inner' + | 'header'; const styles: StyleRulesCallback = theme => ({ root: { @@ -51,10 +60,25 @@ const styles: StyleRulesCallback = theme => ({ textAlign: 'right', marginBottom: 24, marginTop: theme.spacing.unit + }, + panel: { + flexGrow: 1, + width: '100%', + backgroundColor: theme.color.white, + marginBottom: theme.spacing.unit * 3 + }, + inner: { + padding: theme.spacing.unit * 2, + [theme.breakpoints.up('sm')]: { + padding: theme.spacing.unit * 3 + } + }, + header: { + paddingBottom: theme.spacing.unit * 2 } }); -interface Props { +interface Props extends RenderGuardProps { selectedId: number | undefined; selectedUsername?: string; error?: string; @@ -68,9 +92,20 @@ interface Props { publicImages: Linode.Image[]; resetSelectedStackScript: () => void; disabled?: boolean; + request: ( + username: string, + params?: any, + filter?: any, + stackScriptGrants?: Linode.Grant[] + ) => Promise>; + category: string; + header: string; } -type CombinedProps = Props & StateProps & WithStyles; +type CombinedProps = Props & + StateProps & + RenderGuardProps & + WithStyles; interface State { stackScript?: Linode.StackScript.Response; @@ -87,9 +122,12 @@ class SelectStackScriptPanel extends React.Component { mounted: boolean = false; componentDidMount() { - if (this.props.selectedId) { + const selected = + this.props.selectedId || + getParamFromUrl(location.search, 'stackScriptID'); + if (selected) { this.setState({ stackScriptLoading: true }); - getStackScript(this.props.selectedId) + getStackScript(selected) .then(stackScript => { this.setState({ stackScript, stackScriptLoading: false }); this.props.onSelect( @@ -111,22 +149,6 @@ class SelectStackScriptPanel extends React.Component { this.mounted = false; } - createTabs = StackScriptTabs.map(tab => ({ - title: tab.title, - render: () => ( - - ) - })); - handleTabChange = () => { /* * if we're coming from a query string, the stackscript will be preselected @@ -141,7 +163,14 @@ class SelectStackScriptPanel extends React.Component { }; render() { - const { error, classes, selectedId } = this.props; + const { + category, + classes, + header, + request, + selectedId, + error + } = this.props; const { stackScript, stackScriptLoading, stackScriptError } = this.state; if (selectedId) { @@ -173,17 +202,13 @@ class SelectStackScriptPanel extends React.Component { deploymentsActive={stackScript.deployments_active} updated={formatDate(stackScript.updated, false)} checked={selectedId === stackScript.id} - updateFor={[selectedId === stackScript.id, classes]} + updateFor={[selectedId === stackScript.id]} stackScriptID={stackScript.id} />
-
@@ -193,23 +218,36 @@ class SelectStackScriptPanel extends React.Component { } return ( - - {stackScriptError && ( - - An error occured while loading the selected StackScript. Please - choose one from the list. + +
+ {error && } + + {header} - )} - - + {stackScriptError && ( + + An error occurred while loading the selected StackScript. + + )} + + + +
+
); } } @@ -226,12 +264,7 @@ const connected = connect(mapStateToProps); const styled = withStyles(styles); -export default compose< - Linode.TodoAny, - Linode.TodoAny, - Linode.TodoAny, - Linode.TodoAny ->( +export default compose( connected, RenderGuard, styled diff --git a/src/features/StackScripts/SelectStackScriptPanel/SelectStackScriptPanelContent.tsx b/src/features/StackScripts/SelectStackScriptPanel/SelectStackScriptPanelContent.tsx index e47481ab3bd..5f4aa7455d8 100644 --- a/src/features/StackScripts/SelectStackScriptPanel/SelectStackScriptPanelContent.tsx +++ b/src/features/StackScripts/SelectStackScriptPanel/SelectStackScriptPanelContent.tsx @@ -19,7 +19,8 @@ interface Props { request: ( username: string, params?: any, - filter?: any + filter?: any, + stackScriptGrants?: Linode.Grant[] ) => Promise>; category: string; disabled?: boolean; diff --git a/src/features/StackScripts/SelectStackScriptPanel/index.ts b/src/features/StackScripts/SelectStackScriptPanel/index.ts index d06eee18238..d39d3c670ae 100644 --- a/src/features/StackScripts/SelectStackScriptPanel/index.ts +++ b/src/features/StackScripts/SelectStackScriptPanel/index.ts @@ -1,3 +1,2 @@ import SelectStackScriptPanel from './SelectStackScriptPanel'; -// export { CommunityStackScripts, LinodeStackScripts, MyStackScripts }; export default SelectStackScriptPanel; diff --git a/src/features/StackScripts/StackScriptBase/StackScriptBase.tsx b/src/features/StackScripts/StackScriptBase/StackScriptBase.tsx index ab20e1fefb6..81793fa83db 100644 --- a/src/features/StackScripts/StackScriptBase/StackScriptBase.tsx +++ b/src/features/StackScripts/StackScriptBase/StackScriptBase.tsx @@ -13,12 +13,15 @@ import Table from 'src/components/Table'; import { isRestrictedUser } from 'src/features/Profile/permissionsHelpers'; import { MapState } from 'src/store/types'; import { sendEvent } from 'src/utilities/analytics'; +import { + getAPIErrorOrDefault, + handleUnauthorizedErrors +} from 'src/utilities/errorUtils'; import StackScriptTableHead from '../Partials/StackScriptTableHead'; import { AcceptedFilters, generateCatchAllFilter, - generateSpecificFilter, - getErrorText + generateSpecificFilter } from '../stackScriptUtils'; import withStyles, { StyleProps } from './StackScriptBase.styles'; @@ -45,7 +48,7 @@ export interface State { currentFilter: any; // @TODO type correctly currentSearchFilter: any; isSorting: boolean; - error?: Error; + error?: Linode.ApiFieldError[]; fieldError: Linode.ApiFieldError | undefined; isSearching: boolean; didSearch: boolean; @@ -98,7 +101,7 @@ const withStackScriptBase = (isSelecting: boolean) => ( componentDidMount() { this.mounted = true; - return this.getDataAtPage(0); + return this.getDataAtPage(1); } componentWillUnmount() { @@ -180,7 +183,10 @@ const withStackScriptBase = (isSelecting: boolean) => ( this.setState({ getMoreStackScriptsFailed: true }); } this.setState({ - error: e.response, + error: getAPIErrorOrDefault( + e, + 'There was an error loading StackScripts' + ), loading: false, gettingMoreStackScripts: false }); @@ -356,7 +362,13 @@ const withStackScriptBase = (isSelecting: boolean) => ( if (!this.mounted) { return; } - this.setState({ error: e, isSearching: false }); + this.setState({ + error: getAPIErrorOrDefault( + e, + 'There was an error loading StackScripts' + ), + isSearching: false + }); }); }; @@ -381,7 +393,14 @@ const withStackScriptBase = (isSelecting: boolean) => ( if (error) { return (
- +
); } diff --git a/src/features/StackScripts/StackScriptPanel/StackScriptActionMenu.tsx b/src/features/StackScripts/StackScriptPanel/StackScriptActionMenu.tsx index 22216d850fe..fa906b4e98e 100644 --- a/src/features/StackScripts/StackScriptPanel/StackScriptActionMenu.tsx +++ b/src/features/StackScripts/StackScriptPanel/StackScriptActionMenu.tsx @@ -1,7 +1,12 @@ +import { path } from 'ramda'; import * as React from 'react'; import { RouteComponentProps, withRouter } from 'react-router-dom'; +import { compose } from 'recompose'; import ActionMenu, { Action } from 'src/components/ActionMenu/ActionMenu'; +import withProfile from 'src/containers/profile.container'; + +import { getStackScriptUrl } from '../stackScriptUtils'; interface Props { stackScriptID: number; @@ -12,6 +17,8 @@ interface Props { canDelete: boolean; canEdit: boolean; isPublic: boolean; + // From Profile HOC + username?: string; } type CombinedProps = Props & RouteComponentProps<{}>; @@ -28,7 +35,8 @@ const StackScriptActionMenu: React.StatelessComponent< stackScriptLabel, canDelete, canEdit, - isPublic + isPublic, + username } = props; const createActions = () => { @@ -38,8 +46,12 @@ const StackScriptActionMenu: React.StatelessComponent< title: 'Deploy New Linode', onClick: (e: React.MouseEvent) => { history.push( - `/linodes/create?type=fromStackScript` + - `&stackScriptID=${stackScriptID}&stackScriptUsername=${stackScriptUsername}` + getStackScriptUrl( + stackScriptUsername, + stackScriptID, + stackScriptLabel, + username + ) ); e.preventDefault(); } @@ -83,4 +95,14 @@ const StackScriptActionMenu: React.StatelessComponent< return ; }; -export default withRouter(StackScriptActionMenu); +const enhanced = compose( + withRouter, + withProfile((ownProps, profile) => { + return { + ...ownProps, + username: path(['username'], profile) + }; + }) +); + +export default enhanced(StackScriptActionMenu); diff --git a/src/features/StackScripts/StackScriptPanel/StackScriptPanel.tsx b/src/features/StackScripts/StackScriptPanel/StackScriptPanel.tsx index b24aa52d57c..29f34f98c3c 100644 --- a/src/features/StackScripts/StackScriptPanel/StackScriptPanel.tsx +++ b/src/features/StackScripts/StackScriptPanel/StackScriptPanel.tsx @@ -9,7 +9,10 @@ import { import RenderGuard from 'src/components/RenderGuard'; import TabbedPanel from 'src/components/TabbedPanel'; import { MapState } from 'src/store/types'; -import { StackScriptTabs } from '../stackScriptUtils'; +import { + getCommunityStackscripts, + getMineAndAccountStackScripts +} from '../stackScriptUtils'; import StackScriptPanelContent from './StackScriptPanelContent'; export interface ExtendedLinode extends Linode.Linode { @@ -93,6 +96,19 @@ class SelectStackScriptPanel extends React.Component { } } +export const StackScriptTabs = [ + { + title: 'Account StackScripts', + request: getMineAndAccountStackScripts, + category: 'account' + }, + { + title: 'Community StackScripts', + request: getCommunityStackscripts, + category: 'community' + } +]; + interface StateProps { username: string; } diff --git a/src/features/StackScripts/StackScriptsDetail.tsx b/src/features/StackScripts/StackScriptsDetail.tsx index fe6dea917fb..a84c4f587d0 100644 --- a/src/features/StackScripts/StackScriptsDetail.tsx +++ b/src/features/StackScripts/StackScriptsDetail.tsx @@ -1,4 +1,4 @@ -import { compose } from 'ramda'; +import { compose, path } from 'ramda'; import * as React from 'react'; import { RouteComponentProps } from 'react-router-dom'; @@ -14,10 +14,13 @@ import setDocs, { SetDocsProps } from 'src/components/DocsSidebar/setDocs'; import Grid from 'src/components/Grid'; import NotFound from 'src/components/NotFound'; import StackScript from 'src/components/StackScript'; +import withProfile from 'src/containers/profile.container'; import { StackScripts as StackScriptsDocs } from 'src/documentation'; import { getStackScript } from 'src/services/stackscripts'; +import { getStackScriptUrl } from './stackScriptUtils'; + interface MatchProps { stackScriptId: string; } @@ -49,7 +52,15 @@ const styles: StyleRulesCallback = theme => ({ } }); -type CombinedProps = RouteProps & WithStyles & SetDocsProps; +interface ProfileProps { + // From Profile container + username?: string; +} + +type CombinedProps = ProfileProps & + RouteProps & + WithStyles & + SetDocsProps; export class StackScriptsDetail extends React.Component { state: State = { @@ -69,6 +80,21 @@ export class StackScriptsDetail extends React.Component { }); } + handleClick = () => { + const { history, username } = this.props; + const { stackScript } = this.state; + if (!stackScript) { + return; + } + const url = getStackScriptUrl( + stackScript.username, + stackScript.id, + stackScript.label, + username + ); + history.push(url); + }; + render() { const { classes } = this.props; const { loading, stackScript } = this.state; @@ -97,9 +123,7 @@ export class StackScriptsDetail extends React.Component {