diff --git a/workspaces/sandbox/.changeset/README.md b/workspaces/sandbox/.changeset/README.md
new file mode 100644
index 000000000..e5b6d8d6a
--- /dev/null
+++ b/workspaces/sandbox/.changeset/README.md
@@ -0,0 +1,8 @@
+# Changesets
+
+Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
+with multi-package repos, or single-package repos to help you version and publish your code. You can
+find the full documentation for it [in our repository](https://github.com/changesets/changesets)
+
+We have a quick list of common questions to get you started engaging with this project in
+[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
diff --git a/workspaces/sandbox/.changeset/config.json b/workspaces/sandbox/.changeset/config.json
new file mode 100644
index 000000000..8208df00f
--- /dev/null
+++ b/workspaces/sandbox/.changeset/config.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
+ "changelog": "@changesets/cli/changelog",
+ "commit": false,
+ "fixed": [],
+ "linked": [],
+ "access": "public",
+ "baseBranch": "main",
+ "updateInternalDependencies": "patch",
+ "privatePackages": {
+ "tag": false,
+ "version": false
+ }
+}
diff --git a/workspaces/sandbox/.changeset/smooth-days-deny.md b/workspaces/sandbox/.changeset/smooth-days-deny.md
new file mode 100644
index 000000000..902292101
--- /dev/null
+++ b/workspaces/sandbox/.changeset/smooth-days-deny.md
@@ -0,0 +1,5 @@
+---
+'@red-hat-developer-hub/backstage-plugin-sandbox': patch
+---
+
+Added Home and Activities pages
diff --git a/workspaces/sandbox/.dockerignore b/workspaces/sandbox/.dockerignore
new file mode 100644
index 000000000..05edb6265
--- /dev/null
+++ b/workspaces/sandbox/.dockerignore
@@ -0,0 +1,8 @@
+.git
+.yarn/cache
+.yarn/install-state.gz
+node_modules
+packages/*/src
+packages/*/node_modules
+plugins
+*.local.yaml
diff --git a/workspaces/sandbox/.eslintignore b/workspaces/sandbox/.eslintignore
new file mode 100644
index 000000000..e5b19947f
--- /dev/null
+++ b/workspaces/sandbox/.eslintignore
@@ -0,0 +1 @@
+playwright.config.ts
diff --git a/workspaces/sandbox/.eslintrc.js b/workspaces/sandbox/.eslintrc.js
new file mode 100644
index 000000000..59b86f841
--- /dev/null
+++ b/workspaces/sandbox/.eslintrc.js
@@ -0,0 +1 @@
+module.exports = require('../../.eslintrc.cjs');
diff --git a/workspaces/sandbox/.gitignore b/workspaces/sandbox/.gitignore
new file mode 100644
index 000000000..fbf813909
--- /dev/null
+++ b/workspaces/sandbox/.gitignore
@@ -0,0 +1,54 @@
+# macOS
+.DS_Store
+
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+# Coverage directory generated when running tests with coverage
+coverage
+
+# Dependencies
+node_modules/
+
+# Yarn 3 files
+.pnp.*
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/sdks
+!.yarn/versions
+
+# Node version directives
+.nvmrc
+
+# dotenv environment variables file
+.env
+.env.test
+
+# Build output
+dist
+dist-types
+
+# Temporary change files created by Vim
+*.swp
+
+# MkDocs build output
+site
+
+# Local configuration files
+*.local.yaml
+
+# Sensitive credentials
+*-credentials.yaml
+
+# vscode database functionality support files
+*.session.sql
+
+# E2E test reports
+e2e-test-report/
diff --git a/workspaces/sandbox/.prettierignore b/workspaces/sandbox/.prettierignore
new file mode 100644
index 000000000..1cfaa8947
--- /dev/null
+++ b/workspaces/sandbox/.prettierignore
@@ -0,0 +1,5 @@
+dist
+dist-types
+coverage
+.vscode
+.eslintrc.js
diff --git a/workspaces/sandbox/README.md b/workspaces/sandbox/README.md
new file mode 100644
index 000000000..94044a3fa
--- /dev/null
+++ b/workspaces/sandbox/README.md
@@ -0,0 +1,16 @@
+# [Backstage](https://backstage.io)
+
+This is your newly scaffolded Backstage App, Good Luck!
+
+To start the app, run:
+
+```sh
+yarn install
+yarn dev
+```
+
+To generate knip reports for this app, run:
+
+```sh
+yarn backstage-repo-tools knip-reports
+```
diff --git a/workspaces/sandbox/app-config.yaml b/workspaces/sandbox/app-config.yaml
new file mode 100644
index 000000000..88067f483
--- /dev/null
+++ b/workspaces/sandbox/app-config.yaml
@@ -0,0 +1,79 @@
+app:
+ title: Developer Sandbox
+ baseUrl: http://localhost:3000
+
+organization:
+ name: Red Hat
+
+backend:
+ # Used for enabling authentication, secret is shared by all backend plugins
+ # See https://backstage.io/docs/auth/service-to-service-auth for
+ # information on the format
+ # auth:
+ # keys:
+ # - secret: ${BACKEND_SECRET}
+ baseUrl: http://localhost:7007
+ listen:
+ port: 7007
+ # Uncomment the following host directive to bind to specific interfaces
+ # host: 127.0.0.1
+ csp:
+ connect-src: ["'self'", 'http:', 'https:']
+ # Content-Security-Policy directives follow the Helmet format: https://helmetjs.github.io/#reference
+ # Default Helmet Content-Security-Policy values can be removed by setting the key to false
+ cors:
+ origin: http://localhost:3000
+ methods: [GET, HEAD, PATCH, POST, PUT, DELETE]
+ credentials: true
+ # This is for local development only, it is not recommended to use this in production
+ # The production database configuration is stored in app-config.production.yaml
+ database:
+ client: better-sqlite3
+ connection: ':memory:'
+ # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir
+
+integrations:
+ github:
+ - host: github.com
+ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information
+ # about setting up the GitHub integration here: https://backstage.io/docs/integrations/github/locations#configuration
+ token: ${GITHUB_TOKEN}
+ ### Example for how to add your GitHub Enterprise instance using the API:
+ # - host: ghe.example.net
+ # apiBaseUrl: https://ghe.example.net/api/v3
+ # token: ${GHE_TOKEN}
+
+ ### Example for how to add a proxy endpoint for the frontend.
+ ### A typical reason to do this is to handle HTTPS and CORS for internal services.
+ # endpoints:
+ # '/test':
+ # target: 'https://example.com'
+ # changeOrigin: true
+
+# Reference documentation http://backstage.io/docs/features/techdocs/configuration
+# Note: After experimenting with basic setup, use CI/CD to generate docs
+# and an external cloud storage when deploying TechDocs for production use-case.
+# https://backstage.io/docs/features/techdocs/how-to-guides#how-to-migrate-from-techdocs-basic-to-recommended-deployment-approach
+techdocs:
+ builder: 'local' # Alternatives - 'external'
+ generator:
+ runIn: 'docker' # Alternatives - 'local'
+ publisher:
+ type: 'local' # Alternatives - 'googleGcs' or 'awsS3'. Read documentation for using alternatives.
+
+auth:
+ # see https://backstage.io/docs/auth/ to learn about auth providers
+ providers:
+ # See https://backstage.io/docs/auth/guest/provider
+ guest: {}
+
+scaffolder:
+ {}
+ # see https://backstage.io/docs/features/software-templates/configuration for software template options
+
+catalog:
+ import:
+ entityFilename: catalog-info.yaml
+ pullRequestBranchName: backstage-integration
+ rules:
+ - allow: [Component, System, API, Resource, Location]
diff --git a/workspaces/sandbox/backstage.json b/workspaces/sandbox/backstage.json
new file mode 100644
index 000000000..4fa1b2742
--- /dev/null
+++ b/workspaces/sandbox/backstage.json
@@ -0,0 +1 @@
+{ "version": "1.35.0" }
diff --git a/workspaces/sandbox/catalog-info.yaml b/workspaces/sandbox/catalog-info.yaml
new file mode 100644
index 000000000..72a809fb6
--- /dev/null
+++ b/workspaces/sandbox/catalog-info.yaml
@@ -0,0 +1,14 @@
+apiVersion: backstage.io/v1alpha1
+kind: Component
+metadata:
+ name: sandbox
+ description: An example of a Backstage application.
+ # Example for optional annotations
+ # annotations:
+ # github.com/project-slug: backstage/backstage
+ # backstage.io/techdocs-ref: dir:.
+spec:
+ type: website
+ owner: john@example.com
+ lifecycle: experimental
+
diff --git a/workspaces/sandbox/package.json b/workspaces/sandbox/package.json
new file mode 100644
index 000000000..d6d2eaf89
--- /dev/null
+++ b/workspaces/sandbox/package.json
@@ -0,0 +1,63 @@
+{
+ "name": "@internal/sandbox",
+ "version": "1.0.0",
+ "private": true,
+ "engines": {
+ "node": "18 || 20"
+ },
+ "scripts": {
+ "start": "yarn workspace app start",
+ "start-backend": "yarn workspace backend start",
+ "build:backend": "yarn workspace backend build",
+ "tsc": "tsc",
+ "tsc:full": "tsc --skipLibCheck false --incremental false",
+ "build:all": "backstage-cli repo build --all",
+ "build:api-reports": "yarn build:api-reports:only --tsc",
+ "build:api-reports:only": "backstage-repo-tools api-reports -o ae-wrong-input-file-type --validate-release-tags",
+ "clean": "backstage-cli repo clean",
+ "test": "backstage-cli repo test",
+ "test:all": "backstage-cli repo test --coverage",
+ "fix": "backstage-cli repo fix",
+ "lint": "backstage-cli repo lint --since origin/main",
+ "lint:all": "backstage-cli repo lint",
+ "prettier:check": "prettier --check .",
+ "new": "backstage-cli new --scope @red-hat-developer-hub",
+ "postinstall": "cd ../../ && yarn install"
+ },
+ "workspaces": {
+ "packages": [
+ "packages/*",
+ "plugins/*"
+ ]
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/redhat-developer/rhdh-plugins",
+ "directory": "workspaces/sandbox"
+ },
+ "devDependencies": {
+ "@backstage/cli": "^0.29.5",
+ "@backstage/e2e-test-utils": "^0.1.1",
+ "@backstage/repo-tools": "^0.10.0",
+ "@changesets/cli": "^2.27.1",
+ "@spotify/prettier-config": "^12.0.0",
+ "knip": "^5.27.4",
+ "node-gyp": "^9.0.0",
+ "prettier": "^2.3.2",
+ "typescript": "~5.3.0"
+ },
+ "resolutions": {
+ "@types/react": "^18",
+ "@types/react-dom": "^18"
+ },
+ "prettier": "@spotify/prettier-config",
+ "lint-staged": {
+ "*.{js,jsx,ts,tsx,mjs,cjs}": [
+ "eslint --fix",
+ "prettier --write"
+ ],
+ "*.{json,md}": [
+ "prettier --write"
+ ]
+ }
+}
diff --git a/workspaces/sandbox/plugins/README.md b/workspaces/sandbox/plugins/README.md
new file mode 100644
index 000000000..d7865fdba
--- /dev/null
+++ b/workspaces/sandbox/plugins/README.md
@@ -0,0 +1,9 @@
+# The Plugins Folder
+
+This is where your own plugins and their associated modules live, each in a
+separate folder of its own.
+
+If you want to create a new plugin here, go to your project root directory, run
+the command `yarn new`, and follow the on-screen instructions.
+
+You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)!
diff --git a/workspaces/sandbox/plugins/sandbox/.eslintrc.js b/workspaces/sandbox/plugins/sandbox/.eslintrc.js
new file mode 100644
index 000000000..e2a53a6ad
--- /dev/null
+++ b/workspaces/sandbox/plugins/sandbox/.eslintrc.js
@@ -0,0 +1 @@
+module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
diff --git a/workspaces/sandbox/plugins/sandbox/README.md b/workspaces/sandbox/plugins/sandbox/README.md
new file mode 100644
index 000000000..2ef3794e4
--- /dev/null
+++ b/workspaces/sandbox/plugins/sandbox/README.md
@@ -0,0 +1,13 @@
+# sandbox
+
+Welcome to the sandbox plugin!
+
+_This plugin was created through the Backstage CLI_
+
+## Getting started
+
+Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/sandbox](http://localhost:3000/sandbox).
+
+You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
+This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
+It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory.
diff --git a/workspaces/sandbox/plugins/sandbox/dev/index.tsx b/workspaces/sandbox/plugins/sandbox/dev/index.tsx
new file mode 100644
index 000000000..2ae682d97
--- /dev/null
+++ b/workspaces/sandbox/plugins/sandbox/dev/index.tsx
@@ -0,0 +1,42 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * 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 from 'react';
+import HomeOutlinedIcon from '@mui/icons-material/HomeOutlined';
+import StarOutlineOutlinedIcon from '@mui/icons-material/StarOutlineOutlined';
+import { createDevApp } from '@backstage/dev-utils';
+import { getAllThemes } from '@redhat-developer/red-hat-developer-hub-theme';
+import {
+ sandboxPlugin,
+ SandboxPage,
+ SandboxActivitiesPage,
+} from '../src/plugin';
+
+createDevApp()
+ .registerPlugin(sandboxPlugin)
+ .addThemes(getAllThemes())
+ .addPage({
+ element: ,
+ title: 'Home',
+ icon: HomeOutlinedIcon,
+ path: '/home',
+ })
+ .addPage({
+ element: ,
+ title: 'Activities',
+ icon: StarOutlineOutlinedIcon,
+ path: '/activities',
+ })
+ .render();
diff --git a/workspaces/sandbox/plugins/sandbox/package.json b/workspaces/sandbox/plugins/sandbox/package.json
new file mode 100644
index 000000000..25ab47cb2
--- /dev/null
+++ b/workspaces/sandbox/plugins/sandbox/package.json
@@ -0,0 +1,69 @@
+{
+ "name": "@red-hat-developer-hub/backstage-plugin-sandbox",
+ "version": "0.0.1",
+ "main": "src/index.ts",
+ "types": "src/index.ts",
+ "license": "Apache-2.0",
+ "publishConfig": {
+ "access": "public",
+ "main": "dist/index.esm.js",
+ "types": "dist/index.d.ts"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/redhat-developer/rhdh-plugins",
+ "directory": "workspaces/sandbox/plugins/sandbox"
+ },
+ "backstage": {
+ "role": "frontend-plugin",
+ "pluginId": "sandbox",
+ "pluginPackages": [
+ "@red-hat-developer-hub/backstage-plugin-sandbox"
+ ]
+ },
+ "sideEffects": false,
+ "scripts": {
+ "start": "backstage-cli package start",
+ "build": "backstage-cli package build",
+ "lint": "backstage-cli package lint",
+ "test": "backstage-cli package test",
+ "clean": "backstage-cli package clean",
+ "prepack": "backstage-cli package prepack",
+ "postpack": "backstage-cli package postpack"
+ },
+ "dependencies": {
+ "@backstage/core-components": "^0.16.3",
+ "@backstage/core-plugin-api": "^1.10.3",
+ "@backstage/theme": "^0.6.3",
+ "@material-ui/core": "^4.12.4",
+ "@mui/icons-material": "^5.16.14",
+ "@mui/material": "^5.16.14",
+ "libphonenumber-js": "^1.11.20",
+ "react-phone-number-input": "^3.4.11",
+ "react-use": "^17.2.4"
+ },
+ "peerDependencies": {
+ "react": "^16.13.1 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^18.0.2",
+ "react-router": "^6.3.0",
+ "react-router-dom": "^6.3.0"
+ },
+ "devDependencies": {
+ "@backstage/cli": "^0.29.5",
+ "@backstage/core-app-api": "^1.15.4",
+ "@backstage/dev-utils": "^1.1.6",
+ "@backstage/test-utils": "^1.7.4",
+ "@redhat-developer/red-hat-developer-hub-theme": "0.4.0",
+ "@testing-library/jest-dom": "^6.0.0",
+ "@testing-library/react": "^14.0.0",
+ "@testing-library/user-event": "^14.0.0",
+ "msw": "^1.0.0",
+ "react": "^16.13.1 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^18.0.2",
+ "react-router": "^6.3.0",
+ "react-router-dom": "^6.3.0"
+ },
+ "files": [
+ "dist"
+ ]
+}
diff --git a/workspaces/sandbox/plugins/sandbox/src/assets/images/art0.png b/workspaces/sandbox/plugins/sandbox/src/assets/images/art0.png
new file mode 100644
index 000000000..8112143cc
Binary files /dev/null and b/workspaces/sandbox/plugins/sandbox/src/assets/images/art0.png differ
diff --git a/workspaces/sandbox/plugins/sandbox/src/assets/images/art1.png b/workspaces/sandbox/plugins/sandbox/src/assets/images/art1.png
new file mode 100644
index 000000000..5606aa9a5
Binary files /dev/null and b/workspaces/sandbox/plugins/sandbox/src/assets/images/art1.png differ
diff --git a/workspaces/sandbox/plugins/sandbox/src/assets/images/art2.png b/workspaces/sandbox/plugins/sandbox/src/assets/images/art2.png
new file mode 100644
index 000000000..0a26958b6
Binary files /dev/null and b/workspaces/sandbox/plugins/sandbox/src/assets/images/art2.png differ
diff --git a/workspaces/sandbox/plugins/sandbox/src/assets/images/art3.png b/workspaces/sandbox/plugins/sandbox/src/assets/images/art3.png
new file mode 100644
index 000000000..d3188e093
Binary files /dev/null and b/workspaces/sandbox/plugins/sandbox/src/assets/images/art3.png differ
diff --git a/workspaces/sandbox/plugins/sandbox/src/assets/images/art4.png b/workspaces/sandbox/plugins/sandbox/src/assets/images/art4.png
new file mode 100644
index 000000000..c00c425cf
Binary files /dev/null and b/workspaces/sandbox/plugins/sandbox/src/assets/images/art4.png differ
diff --git a/workspaces/sandbox/plugins/sandbox/src/assets/images/art5.png b/workspaces/sandbox/plugins/sandbox/src/assets/images/art5.png
new file mode 100644
index 000000000..f18c522d5
Binary files /dev/null and b/workspaces/sandbox/plugins/sandbox/src/assets/images/art5.png differ
diff --git a/workspaces/sandbox/plugins/sandbox/src/assets/images/sandbox-banner-image.png b/workspaces/sandbox/plugins/sandbox/src/assets/images/sandbox-banner-image.png
new file mode 100644
index 000000000..a168ee601
Binary files /dev/null and b/workspaces/sandbox/plugins/sandbox/src/assets/images/sandbox-banner-image.png differ
diff --git a/workspaces/sandbox/plugins/sandbox/src/assets/logos/ansible.png b/workspaces/sandbox/plugins/sandbox/src/assets/logos/ansible.png
new file mode 100644
index 000000000..04ff6a9b3
Binary files /dev/null and b/workspaces/sandbox/plugins/sandbox/src/assets/logos/ansible.png differ
diff --git a/workspaces/sandbox/plugins/sandbox/src/assets/logos/developer-hub.png b/workspaces/sandbox/plugins/sandbox/src/assets/logos/developer-hub.png
new file mode 100644
index 000000000..00f3dda64
Binary files /dev/null and b/workspaces/sandbox/plugins/sandbox/src/assets/logos/developer-hub.png differ
diff --git a/workspaces/sandbox/plugins/sandbox/src/assets/logos/devspaces.png b/workspaces/sandbox/plugins/sandbox/src/assets/logos/devspaces.png
new file mode 100644
index 000000000..acd15831a
Binary files /dev/null and b/workspaces/sandbox/plugins/sandbox/src/assets/logos/devspaces.png differ
diff --git a/workspaces/sandbox/plugins/sandbox/src/assets/logos/openshift-ai.png b/workspaces/sandbox/plugins/sandbox/src/assets/logos/openshift-ai.png
new file mode 100644
index 000000000..b3cf33a50
Binary files /dev/null and b/workspaces/sandbox/plugins/sandbox/src/assets/logos/openshift-ai.png differ
diff --git a/workspaces/sandbox/plugins/sandbox/src/assets/logos/openshift-virtualization.png b/workspaces/sandbox/plugins/sandbox/src/assets/logos/openshift-virtualization.png
new file mode 100644
index 000000000..47f03d46a
Binary files /dev/null and b/workspaces/sandbox/plugins/sandbox/src/assets/logos/openshift-virtualization.png differ
diff --git a/workspaces/sandbox/plugins/sandbox/src/assets/logos/openshift.png b/workspaces/sandbox/plugins/sandbox/src/assets/logos/openshift.png
new file mode 100644
index 000000000..e817b6417
Binary files /dev/null and b/workspaces/sandbox/plugins/sandbox/src/assets/logos/openshift.png differ
diff --git a/workspaces/sandbox/plugins/sandbox/src/components/Modals/AccessCodeInputModal.tsx b/workspaces/sandbox/plugins/sandbox/src/components/Modals/AccessCodeInputModal.tsx
new file mode 100644
index 000000000..28f44cc9e
--- /dev/null
+++ b/workspaces/sandbox/plugins/sandbox/src/components/Modals/AccessCodeInputModal.tsx
@@ -0,0 +1,163 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * 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, { useEffect, useRef, useState } from 'react';
+import { useTheme } from '@mui/material/styles';
+import Button from '@mui/material/Button';
+import Dialog from '@mui/material/Dialog';
+import DialogTitle from '@mui/material/DialogTitle';
+import DialogContent from '@mui/material/DialogContent';
+import DialogContentText from '@mui/material/DialogContentText';
+import DialogActions from '@mui/material/DialogActions';
+import TextField from '@mui/material/TextField';
+import Stack from '@mui/material/Stack';
+import IconButton from '@mui/material/IconButton';
+import CloseIcon from '@mui/icons-material/Close';
+
+type AccessCodeInputModalProps = {
+ open: boolean;
+ setOpen: React.Dispatch>;
+};
+
+export const AccessCodeInputModal: React.FC = ({
+ open,
+ setOpen,
+}) => {
+ const theme = useTheme();
+ const [otp, setOtp] = useState(['', '', '', '', '', '']);
+ const inputRefs = useRef([]);
+
+ useEffect(() => {
+ if (open) {
+ // Focus on the first input box when modal opens
+ setTimeout(() => inputRefs.current[0]?.focus(), 100);
+ }
+ }, [open]);
+
+ const handleChange = (
+ index: number,
+ event: React.ChangeEvent,
+ ) => {
+ const value = event.target.value;
+ if (!/^\d*$/.test(value)) return; // Allow only digits
+
+ const newOtp = [...otp];
+ newOtp[index] = value;
+ setOtp(newOtp);
+
+ // Move focus to next input
+ if (value && index < otp.length - 1) {
+ inputRefs.current[index + 1].focus();
+ }
+ };
+
+ // Handle Backspace: Move focus to previous box if empty
+ const handleKeyDown = (
+ index: number,
+ event: React.KeyboardEvent,
+ ) => {
+ if (event.key === 'Backspace' && !otp[index] && index > 0) {
+ inputRefs.current[index - 1].focus();
+ }
+ };
+
+ const handleClose = () => {
+ setOpen(false);
+ setOtp(['', '', '', '', '', '']);
+ };
+
+ const handleStartTrialClick = () => {
+ setOpen(false);
+ setOtp(['', '', '', '', '', '']);
+ };
+
+ return (
+
+ );
+};
diff --git a/workspaces/sandbox/plugins/sandbox/src/components/Modals/PhoneVerificationModal.tsx b/workspaces/sandbox/plugins/sandbox/src/components/Modals/PhoneVerificationModal.tsx
new file mode 100644
index 000000000..955a64e1b
--- /dev/null
+++ b/workspaces/sandbox/plugins/sandbox/src/components/Modals/PhoneVerificationModal.tsx
@@ -0,0 +1,218 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * 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, { ForwardedRef, forwardRef, useState } from 'react';
+import { E164Number } from 'libphonenumber-js/types.cjs';
+import {
+ default as RPNInput,
+ Country,
+ getCountryCallingCode,
+} from 'react-phone-number-input';
+import { useTheme } from '@mui/material/styles';
+import Box from '@mui/material/Box';
+import Button from '@mui/material/Button';
+import Select, { SelectChangeEvent } from '@mui/material/Select';
+import MenuItem from '@mui/material/MenuItem';
+import TextField, { TextFieldProps } from '@mui/material/TextField';
+import Typography from '@mui/material/Typography';
+import Dialog from '@mui/material/Dialog';
+import DialogTitle from '@mui/material/DialogTitle';
+import DialogContent from '@mui/material/DialogContent';
+import DialogContentText from '@mui/material/DialogContentText';
+import DialogActions from '@mui/material/DialogActions';
+import IconButton from '@mui/material/IconButton';
+import CloseIcon from '@mui/icons-material/Close';
+import InputAdornment from '@mui/material/InputAdornment';
+
+const FLAG_FETCH_URL =
+ 'https://purecatamphetamine.github.io/country-flag-icons/3x2';
+
+type CountrySelectFieldProps = {
+ disabled?: boolean;
+ value: Country;
+ onChange: (value: Country) => void;
+ options: { label: string; value: Country }[];
+};
+
+type PhoneVerificationModalProps = {
+ open: boolean;
+ setOpen: React.Dispatch>;
+ showOtpModal: React.Dispatch>;
+ phoneNumber: E164Number | undefined;
+ setPhoneNumber: React.Dispatch>;
+};
+
+export const PhoneVerificationModal: React.FC = ({
+ open,
+ setOpen,
+ showOtpModal,
+ phoneNumber,
+ setPhoneNumber,
+}) => {
+ const theme = useTheme();
+ const [countryCode, setCountryCode] = useState('ES');
+
+ const handlePhoneVerificationSubmit = () => {
+ setOpen(false);
+ showOtpModal(true);
+ };
+
+ const handleClose = () => {
+ setOpen(false);
+ setCountryCode('ES');
+ setPhoneNumber(undefined);
+ };
+
+ const PhoneInputField = forwardRef(function PhoneInputField(
+ props: TextFieldProps,
+ ref: ForwardedRef>,
+ ) {
+ return (
+
+
+ +{getCountryCallingCode(countryCode)}
+
+
+ ),
+ }}
+ />
+ );
+ });
+
+ const CountrySelectField = ({
+ value,
+ onChange,
+ options,
+ }: CountrySelectFieldProps) => {
+ const handleSelect = (event: SelectChangeEvent) => {
+ onChange(event.target.value as Country);
+ setCountryCode(event.target.value as Country);
+ };
+ return (
+
+ );
+ };
+
+ return (
+
+ );
+};
diff --git a/workspaces/sandbox/plugins/sandbox/src/components/Modals/VerificationCodeInputModal.tsx b/workspaces/sandbox/plugins/sandbox/src/components/Modals/VerificationCodeInputModal.tsx
new file mode 100644
index 000000000..3a2136058
--- /dev/null
+++ b/workspaces/sandbox/plugins/sandbox/src/components/Modals/VerificationCodeInputModal.tsx
@@ -0,0 +1,201 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * 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, { useEffect, useRef, useState } from 'react';
+import { E164Number, parsePhoneNumber } from 'libphonenumber-js/min';
+import { useTheme } from '@mui/material/styles';
+import Typography from '@mui/material/Typography';
+import Button from '@mui/material/Button';
+import Dialog from '@mui/material/Dialog';
+import DialogTitle from '@mui/material/DialogTitle';
+import DialogContent from '@mui/material/DialogContent';
+import DialogContentText from '@mui/material/DialogContentText';
+import DialogActions from '@mui/material/DialogActions';
+import TextField from '@mui/material/TextField';
+import Stack from '@mui/material/Stack';
+import IconButton from '@mui/material/IconButton';
+import EditIcon from '@mui/icons-material/Edit';
+import CloseIcon from '@mui/icons-material/Close';
+import { Link } from '@backstage/core-components';
+
+type VerificationCodeInputModalProps = {
+ open: boolean;
+ setOpen: React.Dispatch>;
+ showPhoneModal: React.Dispatch>;
+ phoneNumber: E164Number | undefined;
+};
+
+export const VerificationCodeInputModal: React.FC<
+ VerificationCodeInputModalProps
+> = ({ open, setOpen, showPhoneModal, phoneNumber }) => {
+ const theme = useTheme();
+ const [otp, setOtp] = useState(['', '', '', '', '', '']);
+ const inputRefs = useRef([]);
+
+ useEffect(() => {
+ if (open) {
+ // Focus on the first input box when modal opens
+ setTimeout(() => inputRefs.current[0]?.focus(), 100);
+ }
+ }, [open]);
+
+ const handleChange = (
+ index: number,
+ event: React.ChangeEvent,
+ ) => {
+ const value = event.target.value;
+ if (!/^\d*$/.test(value)) return; // Allow only digits
+
+ const newOtp = [...otp];
+ newOtp[index] = value;
+ setOtp(newOtp);
+
+ // Move focus to next input
+ if (value && index < otp.length - 1) {
+ inputRefs.current[index + 1].focus();
+ }
+ };
+
+ // Handle Backspace: Move focus to previous box if empty
+ const handleKeyDown = (
+ index: number,
+ event: React.KeyboardEvent,
+ ) => {
+ if (event.key === 'Backspace' && !otp[index] && index > 0) {
+ inputRefs.current[index - 1].focus();
+ }
+ };
+
+ const handleClose = () => {
+ setOpen(false);
+ setOtp(['', '', '', '', '', '']);
+ };
+
+ const handleStartTrialClick = () => {
+ setOpen(false);
+ setOtp(['', '', '', '', '', '']);
+ };
+
+ const handleEditPhoneNumber = () => {
+ setOpen(false);
+ showPhoneModal(true);
+ };
+
+ const createPhoneString = (phone: E164Number | undefined) => {
+ if (!phone) return '';
+ try {
+ const parsedPhoneNumber = parsePhoneNumber(phone);
+ return `+${parsedPhoneNumber.countryCallingCode} ${parsedPhoneNumber.nationalNumber}`;
+ } catch {
+ return '';
+ }
+ };
+
+ return (
+
+ );
+};
diff --git a/workspaces/sandbox/plugins/sandbox/src/components/SandboxActivities/SandboxActivitiesCard.tsx b/workspaces/sandbox/plugins/sandbox/src/components/SandboxActivities/SandboxActivitiesCard.tsx
new file mode 100644
index 000000000..ffd400d82
--- /dev/null
+++ b/workspaces/sandbox/plugins/sandbox/src/components/SandboxActivities/SandboxActivitiesCard.tsx
@@ -0,0 +1,79 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * 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 from 'react';
+import Card from '@mui/material/Card';
+import CardContent from '@mui/material/CardContent';
+import Typography from '@mui/material/Typography';
+import CardMedia from '@mui/material/CardMedia';
+import { useTheme } from '@mui/material/styles';
+import { Link } from '@backstage/core-components';
+
+type SandboxActivitiesCardProps = {
+ key: number;
+ article: {
+ img: string;
+ title: string;
+ description: string;
+ link: string;
+ };
+};
+
+export const SandboxActivitiesCard: React.FC = ({
+ key,
+ article: { img, title, description, link },
+}) => {
+ const theme = useTheme();
+
+ return (
+
+
+
+
+
+ {title}
+
+
+ {description}
+
+
+
+
+ );
+};
diff --git a/workspaces/sandbox/plugins/sandbox/src/components/SandboxActivities/SandboxActivitiesGrid.tsx b/workspaces/sandbox/plugins/sandbox/src/components/SandboxActivities/SandboxActivitiesGrid.tsx
new file mode 100644
index 000000000..a98ce29ce
--- /dev/null
+++ b/workspaces/sandbox/plugins/sandbox/src/components/SandboxActivities/SandboxActivitiesGrid.tsx
@@ -0,0 +1,81 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * 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 from 'react';
+import Box from '@mui/material/Box';
+import Typography from '@mui/material/Typography';
+import { useTheme } from '@mui/material/styles';
+import { ItemCardGrid } from '@backstage/core-components';
+import { SandboxActivitiesCard } from './SandboxActivitiesCard';
+import { articleData } from './articleData';
+
+const FeaturedArticles: React.FC = () => {
+ const theme = useTheme();
+
+ return (
+
+
+ Featured
+
+
+ {articleData?.featured?.map((article, index) => (
+
+ ))}
+
+
+ );
+};
+
+const Articles: React.FC = () => {
+ const theme = useTheme();
+
+ return (
+
+
+ {articleData?.other?.map((article, index) => (
+
+ ))}
+
+
+ );
+};
+
+export const SandboxActivitiesGrid: React.FC = () => {
+ return (
+ <>
+
+
+ >
+ );
+};
diff --git a/workspaces/sandbox/plugins/sandbox/src/components/SandboxActivities/SandboxActvitiesPage.tsx b/workspaces/sandbox/plugins/sandbox/src/components/SandboxActivities/SandboxActvitiesPage.tsx
new file mode 100644
index 000000000..0dd8b3887
--- /dev/null
+++ b/workspaces/sandbox/plugins/sandbox/src/components/SandboxActivities/SandboxActvitiesPage.tsx
@@ -0,0 +1,39 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * 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 from 'react';
+import { makeStyles } from '@material-ui/core';
+import { Theme } from '@mui/material/styles';
+import { Page, Content } from '@backstage/core-components';
+import { SandboxHeader } from '../SandboxHeader';
+import { SandboxActivitiesGrid } from './SandboxActivitiesGrid';
+
+const useStyles = makeStyles((theme: Theme) => ({
+ content: {
+ backgroundColor: theme.palette.background.default,
+ },
+}));
+
+export const SandboxActivitiesPage = () => {
+ const classes = useStyles();
+ return (
+
+
+
+
+
+
+ );
+};
diff --git a/workspaces/sandbox/plugins/sandbox/src/components/SandboxActivities/articleData.ts b/workspaces/sandbox/plugins/sandbox/src/components/SandboxActivities/articleData.ts
new file mode 100644
index 000000000..b0ec89302
--- /dev/null
+++ b/workspaces/sandbox/plugins/sandbox/src/components/SandboxActivities/articleData.ts
@@ -0,0 +1,83 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * 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 Art0 from '../../assets/images/art0.png';
+import Art1 from '../../assets/images/art1.png';
+import Art2 from '../../assets/images/art2.png';
+import Art3 from '../../assets/images/art3.png';
+import Art4 from '../../assets/images/art4.png';
+import Art5 from '../../assets/images/art5.png';
+
+type ArticleData = {
+ img: string;
+ title: string;
+ description: string;
+ link: string;
+};
+
+type Articles = {
+ featured: ArticleData[];
+ other: ArticleData[];
+};
+
+export const articleData: Articles = {
+ featured: [
+ {
+ img: Art0,
+ title: 'Get started with your Developer Sandbox',
+ description:
+ 'Learn how to set up and use the Developer Sandbox, and learn to develop quicker than ever before.',
+ link: 'https://developers.redhat.com/learn/openshift/get-started-your-developer-sandbox',
+ },
+ {
+ img: Art1,
+ title: 'Streamline automation in OpenShift Dev Spaces with Ansible',
+ description:
+ 'Learn how to transform the way you develop and test Ansible automations by using OpenShift Dev Spaces, which provides isolated and tailored development environments.',
+ link: 'https://developers.redhat.com/learn/openshift/streamline-automation-openshift-dev-spaces-ansible',
+ },
+ {
+ img: Art2,
+ title: 'How to deploy a Java application on Kubernetes in minutes',
+ description:
+ 'Modernize a legacy Java application by creating microservices, moving it into a container, then deploying it to Red Hat OpenShift using only Kubernetes commands.',
+ link: 'https://developers.redhat.com/learn/java/how-deploy-java-application-kubernetes-minutes',
+ },
+ ],
+ other: [
+ {
+ img: Art3,
+ title: 'Foundations of OpenShift',
+ description:
+ 'Learn the foundations of OpenShift through hands-on experience deploying and working with applications.',
+ link: 'https://developers.redhat.com/learn/openshift/foundations-openshift',
+ },
+ {
+ img: Art4,
+ title: 'Using OpenShift Pipelines',
+ description:
+ 'Learn how to use OpenShift Pipelines for automated builds and deployments – known as CI/CD – of container-based applications to reduce mistakes, improve productivity, and promote more thorough testing.',
+ link: 'https://developers.redhat.com/learn/openshift/using-openshift-pipelines',
+ },
+ {
+ img: Art5,
+ title:
+ 'OpenShift virtualization and application modernization using the Developer Sandbox',
+ description:
+ 'Learn how to create and manage your virtual machines (VMs) using Red Hat OpenShift and the Developer Sandbox, a no-cost OpenShift cluster with no need for setup or configuration.',
+ link: 'https://developers.redhat.com/learn/openshift/openshift-virtualization-and-application-modernization-using-developer-sandbox',
+ },
+ ],
+};
diff --git a/workspaces/sandbox/plugins/sandbox/src/components/SandboxCatalog/SandboxCatalogBanner.tsx b/workspaces/sandbox/plugins/sandbox/src/components/SandboxCatalog/SandboxCatalogBanner.tsx
new file mode 100644
index 000000000..89216dcb9
--- /dev/null
+++ b/workspaces/sandbox/plugins/sandbox/src/components/SandboxCatalog/SandboxCatalogBanner.tsx
@@ -0,0 +1,104 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * 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, useEffect } from 'react';
+import Box from '@mui/material/Box';
+import Typography from '@mui/material/Typography';
+import Card from '@mui/material/Card';
+import Grid from '@mui/material/Grid';
+import { useTheme } from '@mui/material/styles';
+import Image from '../../assets/images/sandbox-banner-image.png';
+import { Context } from './SandboxCatalogPage';
+
+export const SandboxCatalogBanner: React.FC = () => {
+ const theme = useTheme();
+ const [buttonClicked] = useContext(Context);
+ const [loaded, setLoaded] = React.useState(false);
+ const [trialDaysLeft, setTrialDaysLeft] = React.useState(0);
+
+ useEffect(() => {
+ if (buttonClicked) {
+ setTrialDaysLeft(30);
+ setLoaded(true);
+ }
+ }, [buttonClicked, setTrialDaysLeft]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ Try Red Hat products
+
+
+ Explore, experiment, and see what's possible
+
+ {loaded ? (
+
+ Your free trial expires in
+
+ {trialDaysLeft} days.{' '}
+
+
+ ) : (
+
+ Click on "Try it" to initiate your free, no commitment 30-day
+ trial.
+
+ )}
+
+
+
+
+ );
+};
diff --git a/workspaces/sandbox/plugins/sandbox/src/components/SandboxCatalog/SandboxCatalogFooter.tsx b/workspaces/sandbox/plugins/sandbox/src/components/SandboxCatalog/SandboxCatalogFooter.tsx
new file mode 100644
index 000000000..bfd32a863
--- /dev/null
+++ b/workspaces/sandbox/plugins/sandbox/src/components/SandboxCatalog/SandboxCatalogFooter.tsx
@@ -0,0 +1,60 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * 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 Box from '@mui/material/Box';
+import Typography from '@mui/material/Typography';
+import { useTheme } from '@mui/material/styles';
+import { Link } from '@backstage/core-components';
+import { Context } from './SandboxCatalogPage';
+import { AccessCodeInputModal } from '../Modals/AccessCodeInputModal';
+
+export const SandboxCatalogFooter = () => {
+ const theme = useTheme();
+ const [accessCodeModalOpen, setAccessCodeModalOpen] = React.useState(false);
+ const [buttonClicked] = useContext(Context);
+
+ if (buttonClicked) {
+ return null;
+ }
+
+ const handleClick = () => {
+ setAccessCodeModalOpen(true);
+ };
+
+ return (
+ <>
+
+
+ Have an activation code?
+
+ {' '}
+ Click here
+
+
+
+
+ >
+ );
+};
diff --git a/workspaces/sandbox/plugins/sandbox/src/components/SandboxCatalog/SandboxCatalogGrid.tsx b/workspaces/sandbox/plugins/sandbox/src/components/SandboxCatalog/SandboxCatalogGrid.tsx
new file mode 100644
index 000000000..0aebf16db
--- /dev/null
+++ b/workspaces/sandbox/plugins/sandbox/src/components/SandboxCatalog/SandboxCatalogGrid.tsx
@@ -0,0 +1,224 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * 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, useState } from 'react';
+import { E164Number } from 'libphonenumber-js/types.cjs';
+import { useTheme } from '@mui/material/styles';
+import Box from '@mui/material/Box';
+import Grid from '@mui/material/Grid';
+import Stack from '@mui/material/Stack';
+import Card from '@mui/material/Card';
+import Button from '@mui/material/Button';
+import Tooltip from '@mui/material/Tooltip';
+import DoneIcon from '@mui/icons-material/Done';
+import Typography from '@mui/material/Typography';
+import CardMedia from '@mui/material/CardMedia';
+import CardContent from '@mui/material/CardContent';
+import CardActions from '@mui/material/CardActions';
+import OpenInNewIcon from '@mui/icons-material/OpenInNew';
+import { Link } from '@backstage/core-components';
+import { PhoneVerificationModal } from '../Modals/PhoneVerificationModal';
+import { VerificationCodeInputModal } from '../Modals/VerificationCodeInputModal';
+import { productData } from './productData';
+import { Context } from './SandboxCatalogPage';
+
+type SandboxCatalogCardProps = {
+ key: React.Key;
+ title: string;
+ image: string;
+ description: {
+ icon: React.ReactNode;
+ value: string;
+ }[];
+ link: string;
+};
+
+const CatalogCardGreenCorner = ({ show }: { show: boolean }) => {
+ const theme = useTheme();
+ if (!show) {
+ return (
+ // Empty box to keep the layout consistent
+
+ );
+ }
+ return (
+
+
+
+
+
+ );
+};
+
+const SandboxCatalogCard: React.FC = ({
+ key,
+ title,
+ image,
+ description,
+ link,
+}) => {
+ const theme = useTheme();
+ const [, setButtonClicked] = useContext(Context);
+ const [buttonText, setButtonText] = useState('Try it');
+ const [showGreenCorner, setShowGreenCorner] = useState(true);
+ const [phoneVerificationModalOpen, setPhoneVerificationModalOpen] =
+ React.useState(false);
+ const [verificationCodeModalOpen, setVerificationCodeModalOpen] =
+ React.useState(false);
+ const [phoneNumber, setPhoneNumber] = useState();
+
+ const handleTryButtonClick = () => {
+ setButtonText('Provisioning...');
+ setShowGreenCorner(!showGreenCorner);
+ setButtonClicked(true);
+ setPhoneVerificationModalOpen(true);
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+ {title}
+
+
+
+
+ {description?.map(point => (
+
+