diff --git a/plugins/scaffolder/src/next/TemplateListPage/RegisterExistingButton.test.tsx b/plugins/scaffolder/src/next/TemplateListPage/RegisterExistingButton.test.tsx
new file mode 100644
index 0000000000..e6022d16fb
--- /dev/null
+++ b/plugins/scaffolder/src/next/TemplateListPage/RegisterExistingButton.test.tsx
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { renderInTestApp } from '@backstage/test-utils';
+import React from 'react';
+import { RegisterExistingButton } from './RegisterExistingButton';
+import { usePermission } from '@backstage/plugin-permission-react';
+
+jest.mock('@backstage/plugin-permission-react', () => ({
+ usePermission: jest.fn(),
+}));
+
+describe('RegisterExistingButton', () => {
+ beforeEach(() => {
+ (usePermission as jest.Mock).mockClear();
+ });
+
+ it('should not render if to is unset', async () => {
+ (usePermission as jest.Mock).mockReturnValue({ allowed: true });
+
+ const { queryByText } = await renderInTestApp(
+ ,
+ );
+
+ expect(await queryByText('Pick me')).not.toBeInTheDocument();
+ });
+
+ it('should not render if permissions are not allowed', async () => {
+ (usePermission as jest.Mock).mockReturnValue({ allowed: false });
+ const { queryByText } = await renderInTestApp(
+ ,
+ );
+
+ expect(await queryByText('Pick me')).not.toBeInTheDocument();
+ });
+
+ it('should render the button with the text', async () => {
+ (usePermission as jest.Mock).mockReturnValue({ allowed: true });
+ const { queryByText } = await renderInTestApp(
+ ,
+ );
+
+ expect(await queryByText('Pick me')).toBeInTheDocument();
+ });
+});
diff --git a/plugins/scaffolder/src/next/TemplateListPage/RegisterExistingButton.tsx b/plugins/scaffolder/src/next/TemplateListPage/RegisterExistingButton.tsx
new file mode 100644
index 0000000000..be6b496a6c
--- /dev/null
+++ b/plugins/scaffolder/src/next/TemplateListPage/RegisterExistingButton.tsx
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { BackstageTheme } from '@backstage/theme';
+import Button from '@material-ui/core/Button';
+import IconButton from '@material-ui/core/IconButton';
+import useMediaQuery from '@material-ui/core/useMediaQuery';
+import React from 'react';
+import { Link as RouterLink, LinkProps } from 'react-router-dom';
+import AddCircleOutline from '@material-ui/icons/AddCircleOutline';
+import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common';
+import { usePermission } from '@backstage/plugin-permission-react';
+
+/**
+ * Properties for {@link RegisterExistingButton}
+ *
+ * @public
+ */
+export type RegisterExistingButtonProps = {
+ title: string;
+} & Partial>;
+
+/**
+ * A button that helps users to register an existing component.
+ * @public
+ */
+export const RegisterExistingButton = (props: RegisterExistingButtonProps) => {
+ const { title, to } = props;
+ const { allowed } = usePermission(catalogEntityCreatePermission);
+ const isXSScreen = useMediaQuery(theme =>
+ theme.breakpoints.down('xs'),
+ );
+
+ if (!to || !allowed) {
+ return null;
+ }
+
+ return isXSScreen ? (
+
+
+
+ ) : (
+
+ );
+};
diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.test.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.test.tsx
new file mode 100644
index 0000000000..48ee53cdb7
--- /dev/null
+++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.test.tsx
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {
+ catalogApiRef,
+ DefaultStarredEntitiesApi,
+ starredEntitiesApiRef,
+} from '@backstage/plugin-catalog-react';
+import { permissionApiRef } from '@backstage/plugin-permission-react';
+import {
+ MockStorageApi,
+ renderInTestApp,
+ TestApiProvider,
+} from '@backstage/test-utils';
+import React from 'react';
+import { TemplateListPage } from './TemplateListPage';
+
+describe('TemplateListPage', () => {
+ it('should render the search bar for templates', async () => {
+ const { getByPlaceholderText } = await renderInTestApp(
+
+
+ ,
+ );
+
+ expect(getByPlaceholderText('Search')).toBeInTheDocument();
+ });
+
+ it('should render the all and starred filters', async () => {
+ const { getByRole } = await renderInTestApp(
+
+
+ ,
+ );
+
+ expect(getByRole('menuitem', { name: 'All' })).toBeInTheDocument();
+ expect(getByRole('menuitem', { name: 'Starred' })).toBeInTheDocument();
+ });
+});
diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx
index dc736408ac..25441a6812 100644
--- a/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx
+++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx
@@ -35,6 +35,9 @@ import {
UserListPicker,
} from '@backstage/plugin-catalog-react';
import { CategoryPicker } from './CategoryPicker';
+import { RegisterExistingButton } from './RegisterExistingButton';
+import { useRouteRef } from '@backstage/core-plugin-api';
+import { registerComponentRouteRef } from '../../routes';
export type TemplateListGroup = {
title?: string;
@@ -60,6 +63,8 @@ const useStyles = makeStyles(theme => ({
export const TemplateListPage = (props: TemplateListPageProps) => {
const styles = useStyles();
+ const registerComponentLink = useRouteRef(registerComponentRouteRef);
+
return (
@@ -74,12 +79,10 @@ export const TemplateListPage = (props: TemplateListPageProps) => {
/>
- {/* {allowed && (
-
- )} */}
+
Create new software components using standard templates. Different
templates create different kinds of components (services,
diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/SecretsContext/SecretsContext.test.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/SecretsContext/SecretsContext.test.tsx
new file mode 100644
index 0000000000..37d35c9015
--- /dev/null
+++ b/plugins/scaffolder/src/next/TemplateWizardPage/SecretsContext/SecretsContext.test.tsx
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import React, { useContext } from 'react';
+import {
+ useTemplateSecrets,
+ SecretsContextProvider,
+ SecretsContext,
+} from './SecretsContext';
+import { renderHook, act } from '@testing-library/react-hooks';
+
+describe('SecretsContext', () => {
+ it('should allow the setting of secrets in the context', async () => {
+ const { result } = renderHook(
+ () => ({
+ hook: useTemplateSecrets(),
+ context: useContext(SecretsContext),
+ }),
+ {
+ wrapper: ({ children }) => (
+ {children}
+ ),
+ },
+ );
+ expect(result.current.context?.secrets.foo).toEqual(undefined);
+
+ act(() => result.current.hook.setSecret({ foo: 'bar' }));
+
+ expect(result.current.context?.secrets.foo).toEqual('bar');
+ });
+});
diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/SecretsContext/SecretsContext.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/SecretsContext/SecretsContext.tsx
new file mode 100644
index 0000000000..6570f9d901
--- /dev/null
+++ b/plugins/scaffolder/src/next/TemplateWizardPage/SecretsContext/SecretsContext.tsx
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import React, {
+ useState,
+ useCallback,
+ useContext,
+ createContext,
+ PropsWithChildren,
+} from 'react';
+
+type SecretsContextContents = {
+ secrets: Record;
+ setSecrets: React.Dispatch>>;
+};
+
+/**
+ * The actual context object.
+ */
+export const SecretsContext = createContext(
+ undefined,
+);
+
+/**
+ * The Context Provider that holds the state for the secrets.
+ *
+ * @public
+ */
+export const SecretsContextProvider = ({ children }: PropsWithChildren<{}>) => {
+ const [secrets, setSecrets] = useState>({});
+
+ return (
+
+ {children}
+
+ );
+};
+
+/**
+ * Hook to access the secrets context.
+ * @public
+ */
+export const useTemplateSecrets = () => {
+ const value = useContext(SecretsContext);
+ if (!value) {
+ throw new Error(
+ 'useTemplateSecrets must be used within a SecretsContextProvider',
+ );
+ }
+
+ const { setSecrets } = value;
+
+ const setSecret = useCallback(
+ (input: Record) => {
+ setSecrets(currentSecrets => ({ ...currentSecrets, ...input }));
+ },
+ [setSecrets],
+ );
+
+ return { setSecret };
+};
diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/SecretsContext/index.ts b/plugins/scaffolder/src/next/TemplateWizardPage/SecretsContext/index.ts
new file mode 100644
index 0000000000..65b530dea1
--- /dev/null
+++ b/plugins/scaffolder/src/next/TemplateWizardPage/SecretsContext/index.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export {
+ useTemplateSecrets,
+ SecretsContext,
+ SecretsContextProvider,
+} from './SecretsContext';
diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx
new file mode 100644
index 0000000000..94f592eb60
--- /dev/null
+++ b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export const TemplateWizardPage = () => {};
diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/index.ts b/plugins/scaffolder/src/next/TemplateWizardPage/index.ts
new file mode 100644
index 0000000000..84ecf6b3d5
--- /dev/null
+++ b/plugins/scaffolder/src/next/TemplateWizardPage/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export { TemplateWizardPage } from './TemplateWizardPage';
+export { useTemplateSecrets } from './SecretsContext';