From fbb0345a13fb8f1b5a3762e31ce2d384e9fad2ff Mon Sep 17 00:00:00 2001 From: Mehdi Torabi <46302001+mehdi-torabiv@users.noreply.github.com> Date: Tue, 30 Jul 2024 10:01:13 +0300 Subject: [PATCH] implement stepper (#38) --- src/components/shared/CustomStepper.tsx | 81 +++++++++++++++++++ .../shared/StepperComponent.test.tsx | 26 ++++++ src/libs/theme.tsx | 6 +- .../Identifiers/Attestation/Attestation.tsx | 69 ++++++++++++++++ src/pages/Identifiers/Attestation/index.ts | 3 + src/router/index.tsx | 5 ++ 6 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 src/components/shared/CustomStepper.tsx create mode 100644 src/components/shared/StepperComponent.test.tsx create mode 100644 src/pages/Identifiers/Attestation/Attestation.tsx create mode 100644 src/pages/Identifiers/Attestation/index.ts diff --git a/src/components/shared/CustomStepper.tsx b/src/components/shared/CustomStepper.tsx new file mode 100644 index 0000000..41c486a --- /dev/null +++ b/src/components/shared/CustomStepper.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { Stepper, Step, StepLabel, StepIconProps, StepConnector, styled, stepConnectorClasses } from '@mui/material'; +import Check from '@mui/icons-material/Check'; + +interface Step { + label: string; +} + +interface StepperComponentProps { + steps: Step[]; + activeStep: number; +} + +const CustomConnector = styled(StepConnector)(({ theme }) => ({ + [`&.${stepConnectorClasses.alternativeLabel}`]: { + top: 22, + }, + [`&.${stepConnectorClasses.active}`]: { + [`& .${stepConnectorClasses.line}`]: { + backgroundImage: 'linear-gradient( 95deg, #4200FF 0%, #4200FF 50%, #4200FF 100%)', + }, + }, + [`&.${stepConnectorClasses.completed}`]: { + [`& .${stepConnectorClasses.line}`]: { + backgroundImage: 'linear-gradient( 95deg, #4200FF 0%, #4200FF 50%, #4200FF 100%)', + }, + }, + [`& .${stepConnectorClasses.line}`]: { + height: 3, + border: 0, + backgroundColor: theme.palette.mode === 'dark' ? theme.palette.grey[800] : '#eaeaf0', + borderRadius: 1, + }, +})); + +const CustomStepIconRoot = styled('div')<{ + ownerState: { completed?: boolean; active?: boolean }; +}>(({ theme, ownerState }) => ({ + backgroundColor: theme.palette.mode === 'dark' ? theme.palette.grey[700] : '#ccc', + zIndex: 1, + color: '#fff', + width: 50, + height: 50, + display: 'flex', + borderRadius: '50%', + justifyContent: 'center', + alignItems: 'center', + ...(ownerState.active && { + backgroundImage: + 'linear-gradient( 136deg, #4200FF 0%, #4200FF 50%, #4200FF 100%)', + boxShadow: '0 4px 10px 0 rgba(0,0,0,.25)', + }), + ...(ownerState.completed && { + backgroundImage: + 'linear-gradient( 136deg, #4200FF 0%, #4200FF 50%, #4200FF 100%)', + }), +})); + +function CustomStepIcon(props: StepIconProps) { + const { active, completed, className } = props; + + return ( + + {completed ? :
{props.icon}
} +
+ ); +} + +const StepperComponent: React.FC = ({ steps, activeStep }) => { + return ( + }> + {steps.map((step, index) => ( + + {step.label} + + ))} + + ); +}; + +export default StepperComponent; diff --git a/src/components/shared/StepperComponent.test.tsx b/src/components/shared/StepperComponent.test.tsx new file mode 100644 index 0000000..6496af9 --- /dev/null +++ b/src/components/shared/StepperComponent.test.tsx @@ -0,0 +1,26 @@ +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import StepperComponent from './CustomStepper'; + +describe('StepperComponent', () => { + const steps = [ + { label: 'Auth' }, + { label: 'Attest' }, + { label: 'Transact' } + ]; + + it('renders all step labels correctly', () => { + render(); + + steps.forEach(step => { + expect(screen.getByText(step.label)).toBeInTheDocument(); + }); + }); + + it('highlights the active step correctly', () => { + render(); + + const activeStep = screen.getByText('Attest').closest('.MuiStep-root'); + expect(activeStep).toHaveClass('MuiStep-horizontal'); + }); +}); diff --git a/src/libs/theme.tsx b/src/libs/theme.tsx index f51245f..0166cac 100644 --- a/src/libs/theme.tsx +++ b/src/libs/theme.tsx @@ -1,7 +1,11 @@ import { createTheme } from '@mui/material/styles'; const theme = createTheme({ - palette: {}, + palette: { + primary: { + main: '#4200FF' + } + }, typography: { fontFamily: ['DM Sans', 'sans-serif'].join(','), }, diff --git a/src/pages/Identifiers/Attestation/Attestation.tsx b/src/pages/Identifiers/Attestation/Attestation.tsx new file mode 100644 index 0000000..c73416e --- /dev/null +++ b/src/pages/Identifiers/Attestation/Attestation.tsx @@ -0,0 +1,69 @@ +import { useState } from 'react'; +import Paper from '@mui/material/Paper'; +import StepperComponent from '../../../components/shared/CustomStepper'; +import Typography from '@mui/material/Typography'; +import Button from '@mui/material/Button'; + +const steps = [{ label: 'Auth' }, { label: 'Attest' }, { label: 'Transact' }]; + +export function Attestation() { + const [activeStep, setActiveStep] = useState(0); + + const handleNext = () => { + setActiveStep((prevActiveStep) => Math.min(prevActiveStep + 1, steps.length - 1)); + }; + + return ( + + +
+ {activeStep === 0 && ( +
+ + Let’s get started! + + + Please sign in with Discord. + + +
+ )} + {activeStep === 1 && ( +
+ + Generate an attestation. + + + An attestation is a proof that links your Discord account to your wallet address. + + +
+ )} + {activeStep === 2 && ( +
+ + Sign Transaction. + + + Signing the transaction will put your attestation on-chain. + + + + This will cost a small amount of gas. + +
+ )} +
+
+ ); +} + +export default Attestation; diff --git a/src/pages/Identifiers/Attestation/index.ts b/src/pages/Identifiers/Attestation/index.ts new file mode 100644 index 0000000..bb6b951 --- /dev/null +++ b/src/pages/Identifiers/Attestation/index.ts @@ -0,0 +1,3 @@ +import { Attestation } from './Attestation'; + +export default Attestation; diff --git a/src/router/index.tsx b/src/router/index.tsx index 36ddbc9..75f3521 100644 --- a/src/router/index.tsx +++ b/src/router/index.tsx @@ -3,6 +3,7 @@ import { createBrowserRouter } from 'react-router-dom'; import Dashboard from '../pages/Dashboard'; import Identifiers from '../pages/Identifiers'; import Permissions from '../pages/Permissions'; +import Attestation from '../pages/Identifiers/Attestation'; import DefaultLayout from '../layouts/DefaultLayout'; @@ -20,6 +21,10 @@ export const router = createBrowserRouter([ path: '/identifiers', element: , }, + { + path: '/attestation', + element: , + }, { path: '/permissions', element: ,