diff --git a/src/components/onboard/AboutContent.tsx b/src/components/onboard/AboutContent.tsx index 82819436..0aed990e 100644 --- a/src/components/onboard/AboutContent.tsx +++ b/src/components/onboard/AboutContent.tsx @@ -1,351 +1,69 @@ import React from 'react'; -import { - Box, - Typography, - Grid, - Button, - Stack, - alpha, - useTheme, - useMediaQuery, -} from '@mui/material'; -import OpenInNewIcon from '@mui/icons-material/OpenInNew'; -import CodeIcon from '@mui/icons-material/Code'; -import MonetizationOnIcon from '@mui/icons-material/MonetizationOn'; -import VerifiedUserIcon from '@mui/icons-material/VerifiedUser'; -import { useMonthlyRewards } from '../../hooks/useMonthlyRewards'; +import { Box, Typography } from '@mui/material'; +import { useLinkBehavior } from '../common/linkBehavior'; +import { OnboardingCard } from '../onboarding-card'; export const AboutContent: React.FC = () => { - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down('sm')); - const monthlyRewards = useMonthlyRewards(); + const minerGuide = useLinkBehavior( + '/onboard?tab=getting-started', + ); + const registerRepo = useLinkBehavior( + '/repository-registration', + ); + const minerDocs = useLinkBehavior( + 'https://docs.gittensor.io/miner.html', + ); + const maintainerDocs = useLinkBehavior( + 'https://docs.gittensor.io/register-repository.html', + ); return ( - + + + Get started with Gittensor + + + Pick your path. Miners earn alpha tokens by contributing code. + Maintainers get free, incentivized contributions to their open source + projects. + + - {/* 1. Context: What is Gittensor? */} - - - The Marketplace for Open Source - - - - - Open source software powers the world, yet its builders are - rarely compensated for the immense value they create. Gittensor - changes this by turning code contributions into direct, on-chain - rewards. - - - Submit Pull Requests to whitelisted repositories and earn when - they merge. Discover open issues that others later solve and - earn from a separate pool. Two ways to earn, one network:{' '} - - Code, Merge, Earn. - - - - - - - How It Works - - - {[ - { - role: 'Miners (You)', - desc: 'Merge PRs to OSS repos and discover open issues for others to solve.', - }, - { - role: 'Validators', - desc: 'Score contributions, verify merges, and set on-chain weights.', - }, - { - role: 'The Network', - desc: 'Distributes emissions across two pools: OSS contributions and issue discovery.', - }, - ].map((item, i) => ( - - - - - {item.role} - - - {item.desc} - - - - ))} - - - - - - - {/* 2. Recruitment: Why Mine? */} - - - Why Become a Miner? - - - {[ - { - icon: , - title: 'Direct Incentives', - desc: monthlyRewards - ? `Stop coding for free. Compete for a share of the $${monthlyRewards.toLocaleString( - undefined, - { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }, - )} monthly reward pool through OSS contributions or Issue Discovery.` - : 'Stop coding for free. Earn alpha tokens through two tracks: merge PRs to OSS repositories or discover issues for others to solve.', - }, - { - icon: , - title: 'On-Chain Resume', - desc: 'Build a verifiable reputation. Your contributions are permanently recorded on-chain, creating proof of your engineering skills.', - }, - { - icon: , - title: 'Freedom to Build', - desc: 'Work on your terms. No managers, no set hours. Contribute code and get paid for the value you create.', - }, - ].map((card, i) => ( - - - - {card.icon} - - - {card.title} - - - {card.desc} - - - - ))} - - - - {/* 3. CTA: Check the Docs / Get Started */} - theme.palette.status.info} + content={{ + kicker: 'Ready to contribute?', + headline: 'Become a miner', + body: 'Read the quickstart guide to get set up. No complex infrastructure or always-on servers required.', + primaryLabel: 'Miner guide', + primaryLink: minerGuide, + secondaryLabel: 'Read docs', + secondaryLink: minerDocs, }} - > - - Ready to Start earning? - - - We have prepared a comprehensive guide to help you set up your - miner, register on the network, and make your first submission. - - - - - {/* Community Section (Footer) */} - + theme.palette.status.merged} + content={{ + kicker: 'Maintain a repo?', + headline: 'Become a maintainer', + body: 'Install the GitHub App and submit a quick form. The team reviews each repo before listing.', + primaryLabel: 'Register a repo', + primaryLink: registerRepo, + secondaryLabel: 'Read docs', + secondaryLink: maintainerDocs, }} - > - - Community - - - Stay up to date with announcements and news in the{' '} - - Bittensor community - - . - - - Review our codebase and get started mining by checking out the - readme on our{' '} - - Github - - . - - + /> ); diff --git a/src/components/onboard/GettingStarted.tsx b/src/components/onboard/GettingStarted.tsx index dae4efb0..03f8d8f5 100644 --- a/src/components/onboard/GettingStarted.tsx +++ b/src/components/onboard/GettingStarted.tsx @@ -11,10 +11,20 @@ import { import OpenInNewIcon from '@mui/icons-material/OpenInNew'; import ContentCopyIcon from '@mui/icons-material/ContentCopy'; import CheckIcon from '@mui/icons-material/Check'; -import { alpha, darken } from '@mui/material/styles'; -import { scrollbarSx, tooltipSlotProps } from '../../theme'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; +import { alpha, darken, useTheme } from '@mui/material/styles'; +import { + STATUS_COLORS, + UI_COLORS, + scrollbarSx, + tooltipSlotProps, +} from '../../theme'; import { useClipboardCopy } from '../../hooks/useClipboardCopy'; +const GREEN = STATUS_COLORS.merged; +const BLUE = STATUS_COLORS.info; + const MONO = '"JetBrains Mono", monospace'; const MAINNET_NETUID = 74; @@ -369,32 +379,181 @@ uv pip install -e .`} - You're all set! Start contributing to whitelisted repositories — no - miner process needs to run. Validators score your merged PRs - automatically every 2 hours. + You're all set! No miner process needs to run — validators score + your contributions automatically every 2 hours. There are{' '} + two ways to earn: + + + {/* OSS Path */} + + `1px solid ${alpha(t.palette.primary.main, 0.25)}`, + background: (t) => + `linear-gradient(180deg, ${alpha(t.palette.primary.main, 0.06)} 0%, transparent 100%)`, + }} + > + + + 30% + + + emissions + + + + OSS Contributions + + + Submit pull requests to whitelisted repos. Higher repo weight + and structural code changes earn more. + + + ⟶ 5 valid merged PRs · 80% credibility + + + + {/* Issue Discovery Path */} + + + + 10% + + + emissions + + + + Issue Discovery + + + Open detailed, actionable issues on mirror-enabled repos. You + earn when another miner solves your issue with a merged PR. + + + ⟶ 7+ solved issues · 80% issue credibility + + + +
  • - Browse recognized repositories in the{' '} + Browse whitelisted repositories in the{' '} Repositories tab
  • - OSS eligibility: 5 valid merged PRs (token score ≥ 5) and 80% - credibility (merged / total, with one closed-PR mulligan) -
  • -
  • - Issue Discovery eligibility: 7+ solved issues and 80% issue - credibility + Find open bounties in the Bounties tab
  • See the Scoring tab for how rewards are @@ -438,216 +597,440 @@ const NetworkTabs: React.FC<{ ); export const GettingStarted: React.FC = () => { + const theme = useTheme(); const [activeStep, setActiveStep] = useState(0); + const current = steps[activeStep]; + const isFinal = activeStep === steps.length - 1; + const accent = current.active ? GREEN : BLUE; + const nextStepIndex = Math.min(steps.length - 1, activeStep + 1); + const nextAccent = nextStepIndex === steps.length - 1 ? GREEN : BLUE; + const progressPct = + steps.length > 1 ? (activeStep / (steps.length - 1)) * 100 : 0; return ( - - - Miner Setup - + + {/* HERO — editorial split */} + + + + + 7 steps · about 10 minutes + + + + Set up your + + + miner + + - {/* Step indicators */} - - {/* Connecting Line (Desktop) */} + {/* PROGRESS STRIP */} + + {/* Track */} - `linear-gradient(90deg, ${theme.palette.border.subtle} 0%, ${theme.palette.border.medium} 50%, ${theme.palette.border.subtle} 100%)`, - display: { xs: 'none', md: 'block' }, + top: 22, + left: { xs: 22, md: '7%' }, + right: { xs: 'auto', md: '7%' }, + width: { xs: 2, md: 'auto' }, + height: { xs: 'calc(100% - 22px)', md: 2 }, + bgcolor: alpha(UI_COLORS.white, 0.08), zIndex: 0, }} /> + {/* Filled progress */} + - {steps.map((item, index) => ( - setActiveStep(index)} - sx={{ - display: 'flex', - flexDirection: { xs: 'row', md: 'column' }, - alignItems: 'center', - gap: { xs: 1.5, md: 1 }, - width: { xs: '100%', md: '100%' }, - minWidth: 0, - cursor: 'pointer', - '&:hover .step-circle': { - borderColor: 'border.medium', - }, - }} - > - { + const isActive = activeStep === index; + const isComplete = index < activeStep; + const isLastStep = index === steps.length - 1; + const stepAccent = isLastStep ? GREEN : BLUE; + + return ( + setActiveStep(index)} + disableRipple sx={{ - width: 48, - height: 48, - borderRadius: '50%', - bgcolor: (theme) => - activeStep === index - ? index === steps.length - 1 - ? darken(theme.palette.secondary.main, 0.85) - : darken(theme.palette.primary.main, 0.85) - : theme.palette.background.default, - border: '2px solid', - borderColor: item.active - ? 'secondary.main' - : activeStep === index - ? 'primary.main' - : 'border.subtle', - color: item.active - ? 'secondary.main' - : activeStep === index - ? 'primary.main' - : 'text.tertiary', display: 'flex', + flexDirection: { xs: 'row', md: 'column' }, alignItems: 'center', - justifyContent: 'center', - fontFamily: MONO, - fontWeight: 'bold', - fontSize: '1.1rem', - boxShadow: item.active - ? (theme) => - `0 0 20px ${alpha(theme.palette.secondary.main, 0.15)}` - : activeStep === index - ? (theme) => - `0 0 15px ${alpha(theme.palette.primary.main, 0.2)}` - : 'none', - transition: 'all 0.2s ease', - flexShrink: 0, + gap: { xs: 1.5, md: 1 }, + width: '100%', + cursor: 'pointer', + '&:hover .step-tile': { + borderColor: alpha(stepAccent, 0.5), + }, + '&:focus-visible .step-tile': { + outline: `2px solid ${stepAccent}`, + outlineOffset: 2, + }, }} > - {item.step} - - - - {item.title} - - + ) : ( + String(item.step).padStart(2, '0') + )} + + - {item.subtitle} - - - - ))} + + {item.title} + + + {item.subtitle} + + + + ); + })} + {/* STEP CONTENT CARD */} - - Step {steps[activeStep].step}: {steps[activeStep].title} - - + /> + + + {/* Step header */} + + + {String(current.step).padStart(2, '0')} + + + + {current.subtitle} + + + {current.title} + + + + + {/* Step detail content */} + + + + + {/* Prev / Next navigation */} + + + + + {String(activeStep + 1).padStart(2, '0')} /{' '} + {String(steps.length).padStart(2, '0')} + + + + + + {/* DOCUMENTATION CTA */} - `linear-gradient(180deg, ${alpha(theme.palette.background.default, 0)} 0%, ${theme.palette.surface.subtle} 100%)`, - border: '1px solid', - borderColor: 'border.subtle', + p: { xs: 3, md: 4 }, + borderRadius: 2, + border: `1px solid ${alpha(UI_COLORS.white, 0.08)}`, + background: `linear-gradient(135deg, ${alpha(theme.palette.primary.main, 0.08)} 0%, ${alpha(GREEN, 0.04)} 100%)`, + display: 'flex', + flexDirection: { xs: 'column', md: 'row' }, + alignItems: { xs: 'flex-start', md: 'center' }, + justifyContent: 'space-between', + gap: 2, }} > - - Full Documentation - - - For advanced configuration and troubleshooting, see the complete miner - guide. - + + + Need more detail? + + + Advanced configuration and troubleshooting. + + diff --git a/src/components/onboard/Scoring.tsx b/src/components/onboard/Scoring.tsx index e7111cd7..16d86278 100644 --- a/src/components/onboard/Scoring.tsx +++ b/src/components/onboard/Scoring.tsx @@ -1,206 +1,411 @@ import React, { useState } from 'react'; -import { - Box, - Typography, - Button, - Grid, - Tabs, - Tab, - alpha, - useTheme, -} from '@mui/material'; +import { Box, Typography, ButtonBase, Button, alpha } from '@mui/material'; import OpenInNewIcon from '@mui/icons-material/OpenInNew'; +import { CREDIBILITY_COLORS, UI_COLORS } from '../../theme'; + +const MONO = '"JetBrains Mono", monospace'; +const AMBER = CREDIBILITY_COLORS.moderate; type ScoringCategory = 'oss' | 'discovery'; -interface ScoringCard { +const SCORING_RULES: { + category: ScoringCategory; + num: string; title: string; desc: string; - category: ScoringCategory; -} - -const SCORING_CARDS: ScoringCard[] = [ + highlight?: string; +}[] = [ { category: 'oss', - title: 'Merge PRs', - desc: 'Target high-weight repositories like Bitcoin, Ethereum, or PyTorch. Contribution scores decay over time — merge frequently and promptly. Requires at least 5 valid merged PRs to qualify.', + num: '01', + title: 'Repository Weight', + desc: 'Target high-weight repos like Bitcoin, Ethereum, or PyTorch. Repo weight directly multiplies your contribution score.', + highlight: 'higher weight = higher score', }, { category: 'oss', + num: '02', title: 'Code Quality', - desc: 'Write meaningful code. AST-based token scoring rewards structural changes (functions, classes, logic) over simple text, configs, or whitespace edits.', + desc: 'AST-based token scoring rewards structural changes — functions, classes, logic — over text edits, configs, or whitespace.', + highlight: 'structure beats volume', }, { category: 'oss', + num: '03', + title: 'Time Decay', + desc: 'PR scores decay over a 35-day window. Merge frequently and promptly to maintain rolling earnings.', + highlight: '35 day window', + }, + { + category: 'oss', + num: '04', title: 'Credibility', - desc: 'Keep your merge rate high. You need at least 80% credibility (merged / total, with one closed-PR mulligan) and 5 valid merged PRs to become eligible.', + desc: 'Maintain ≥80% merged-to-total ratio (one closed-PR mulligan). Need 5 valid merged PRs to be eligible.', + highlight: '80% merge rate', }, { category: 'discovery', + num: '01', + title: 'Open Issues', + desc: 'Open detailed, actionable issues on whitelisted repos. Quality issues attract solvers and earn discovery rewards.', + highlight: 'detail wins', + }, + { + category: 'discovery', + num: '02', title: 'Issue Multiplier', - desc: "Link your PR to the issue it resolves (e.g. 'Closes #123') for a 1.33× score boost — or 1.66× if the issue was opened by a project maintainer.", + desc: 'Solvers get a 1.33× boost when their PR closes a linked issue — 1.66× if the issue was opened by a project maintainer.', + highlight: '1.33× / 1.66×', }, { category: 'discovery', - title: 'Issue Discovery', - desc: 'Earn from a dedicated 30% emission pool by finding open issues that others later solve with a merged PR. Requires 7+ solved issues and 80% issue credibility to qualify.', + num: '03', + title: 'Eligibility', + desc: '7+ solved issues and 80% issue credibility unlock the discovery emission pool.', + highlight: '7 solved · 80% cred', }, ]; const CATEGORIES: { value: ScoringCategory; label: string; + allocation: string; + color: string; docsUrl: string; }[] = [ { value: 'oss', label: 'OSS Contributions', + allocation: '30%', + color: UI_COLORS.primary, docsUrl: 'https://docs.gittensor.io/oss-contributions.html', }, { value: 'discovery', label: 'Issue Discovery', + allocation: '10%', + color: AMBER, docsUrl: 'https://docs.gittensor.io/issue-discovery.html', }, ]; export const Scoring: React.FC = () => { - const theme = useTheme(); const [category, setCategory] = useState('oss'); - const activeCategory = - CATEGORIES.find((c) => c.value === category) ?? CATEGORIES[0]; - const visibleCards = SCORING_CARDS.filter((c) => c.category === category); + const activeCategory = CATEGORIES.find((c) => c.value === category)!; + const visibleRules = SCORING_RULES.filter((r) => r.category === category); return ( - - + + + ⟶ How rewards are calculated + + + Maximize your + + + rewards. + + + + {/* Allocation toggle */} + - Maximize Your Rewards - + {CATEGORIES.map((cat) => { + const isActive = category === cat.value; + return ( + setCategory(cat.value)} + disableRipple + sx={{ + p: { xs: 2.5, md: 3 }, + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + background: isActive + ? `linear-gradient(180deg, ${alpha(cat.color, 0.15)} 0%, ${alpha(cat.color, 0.05)} 100%)` + : 'transparent', + borderRight: + cat.value === 'oss' + ? `1px solid ${alpha(UI_COLORS.white, 0.1)}` + : 'none', + position: 'relative', + transition: 'all 0.25s', + '&:hover': { + background: isActive + ? undefined + : alpha(UI_COLORS.white, 0.02), + }, + '&::after': isActive + ? { + content: '""', + position: 'absolute', + bottom: 0, + left: 0, + right: 0, + height: 2, + background: cat.color, + boxShadow: `0 0 12px ${cat.color}`, + } + : {}, + }} + > + + + {cat.allocation} + + + emissions + + + + {cat.label} + + + ); + })} + + {/* Rules */} - setCategory(v)} - sx={{ - width: { xs: '100%', sm: 'auto' }, - '& .MuiTab-root': { - textTransform: 'none', - minWidth: { xs: 0, sm: 'auto' }, - flex: { xs: 1, sm: 'initial' }, - px: { xs: 1, sm: 2 }, - fontSize: { xs: '0.82rem', sm: '0.9rem' }, - color: alpha(theme.palette.common.white, 0.55), - '&.Mui-selected': { color: 'text.primary' }, - }, - '& .MuiTabs-indicator': { - backgroundColor: theme.palette.common.white, - }, - }} - > - {CATEGORIES.map((c) => ( - - ))} - - - - - {visibleCards.map((item) => ( - + {visibleRules.map((rule) => ( + - {item.title} + {rule.num} - {item.desc} + {rule.title} - + + {rule.desc} + + {rule.highlight && ( + + + + {rule.highlight} + + + )} + ))} - + + {/* Deep dive CTA */} - - Dive Deeper - - - Learn about the exact formulas, multipliers, and weight calculations - in our detailed documentation. - + + + Deep dive · {activeCategory.label} + + + Exact formulas, multipliers, and weight calculations. + + diff --git a/src/components/onboarding-card.tsx b/src/components/onboarding-card.tsx new file mode 100644 index 00000000..7dc4e5d7 --- /dev/null +++ b/src/components/onboarding-card.tsx @@ -0,0 +1,143 @@ +import React from 'react'; +import { Box, Button, Stack, Typography } from '@mui/material'; +import { alpha, SxProps, Theme } from '@mui/material/styles'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; +import { useLinkBehavior } from './common/linkBehavior'; + +export type OnboardingCardContent = { + kicker: string; + headline: React.ReactNode; + body: React.ReactNode; + primaryLabel: string; + primaryLink: ReturnType>; + secondaryLabel: string; + secondaryLink: ReturnType>; +}; + +interface OnboardingCardProps { + content: OnboardingCardContent; + /** Theme palette key for the accent stripe, gradient, and primary CTA. Defaults to `status.merged`. */ + accent?: (theme: Theme) => string; + /** Optional sx merged into the outer card (e.g. entrance animation). */ + sx?: SxProps; +} + +const defaultAccent = (theme: Theme) => theme.palette.status.merged; + +export const OnboardingCard: React.FC = ({ + content, + accent = defaultAccent, + sx, +}) => ( + ({ + width: '100%', + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + gap: 3, + p: { xs: 2, md: 2.5 }, + borderRadius: 2, + backgroundColor: theme.palette.surface.subtle, + color: theme.palette.text.primary, + border: `1px solid ${theme.palette.border.medium}`, + borderTop: `3px solid ${accent(theme)}`, + position: 'relative', + overflow: 'hidden', + minHeight: 260, + }), + ...(Array.isArray(sx) ? sx : sx ? [sx] : []), + ]} + > + ({ + position: 'absolute', + top: 0, + left: 0, + right: 0, + height: 100, + background: `linear-gradient(to bottom, ${alpha(accent(theme), 0.1)} 0%, transparent 100%)`, + pointerEvents: 'none', + })} + /> + + + {content.kicker} + + + {content.headline} + + ({ + color: alpha(theme.palette.text.primary, 0.62), + fontSize: '0.82rem', + lineHeight: 1.6, + })} + > + {content.body} + + + + + + + +); diff --git a/src/pages/OnboardPage.tsx b/src/pages/OnboardPage.tsx index 2b48efa9..db7ec422 100644 --- a/src/pages/OnboardPage.tsx +++ b/src/pages/OnboardPage.tsx @@ -1,45 +1,32 @@ import React from 'react'; -import { Box, Tabs, Tab, Card, CardContent } from '@mui/material'; +import { Box, Tab, Tabs } from '@mui/material'; import { Page } from '../components/layout'; import { SEO } from '../components'; import { useSearchParams } from 'react-router-dom'; import { AboutContent } from '../components/onboard/AboutContent'; import { FAQContent } from '../components/onboard/FAQContent'; - import { GettingStarted } from '../components/onboard/GettingStarted'; -import { Scoring } from '../components/onboard/Scoring'; import { LanguageWeightsTable } from '../components/repositories'; -const OnboardPage: React.FC = () => { - const [searchParams, setSearchParams] = useSearchParams(); +const TABS = [ + { key: 'about', label: 'Overview' }, + { key: 'getting-started', label: 'Setup' }, + { key: 'languages', label: 'Languages' }, + { key: 'faq', label: 'FAQ' }, +] as const; - // Determine active tab from URL query param or default to 0 - const tabParam = searchParams.get('tab'); - const tabNameToIndex: Record = { - about: 0, - 'getting-started': 1, - scoring: 2, - languages: 3, - faq: 4, - }; +type TabKey = (typeof TABS)[number]['key']; - const indexToTabName: Record = { - 0: 'about', - 1: 'getting-started', - 2: 'scoring', - 3: 'languages', - 4: 'faq', - }; - - const activeTab = - tabParam && tabNameToIndex[tabParam] !== undefined - ? tabNameToIndex[tabParam] - : 0; +const OnboardPage: React.FC = () => { + const [searchParams, setSearchParams] = useSearchParams(); + const tabParam = searchParams.get('tab') as TabKey | null; + const activeTab: TabKey = + tabParam && TABS.some((t) => t.key === tabParam) ? tabParam : 'about'; - const handleTabChange = (_event: React.SyntheticEvent, newValue: number) => { - const newParams = new URLSearchParams(searchParams); - newParams.set('tab', indexToTabName[newValue]); - setSearchParams(newParams, { replace: true }); + const handleTabChange = (_: React.SyntheticEvent, key: TabKey) => { + const next = new URLSearchParams(searchParams); + next.set('tab', key); + setSearchParams(next, { replace: true }); }; return ( @@ -48,97 +35,41 @@ const OnboardPage: React.FC = () => { title="Getting Started - Gittensor" description="Start mining on Gittensor. Setup guide, documentation, and resources." /> - - + ({ position: 'sticky', top: 0, zIndex: 10, - maxWidth: 1200, - width: '100%', - px: { xs: 2, sm: 3, md: 0 }, - mx: 'auto', - mb: 4, backgroundColor: theme.palette.background.default, - borderBottom: '1px solid', - borderColor: theme.palette.border.light, + '& .MuiTab-root.Mui-selected': { + color: theme.palette.common.white, + }, })} > - ({ - px: 0, - '& .MuiTabs-scrollButtons.Mui-disabled': { - display: 'none', - }, - '& .MuiTabs-flexContainer': { - gap: { xs: 3, sm: 4 }, - }, - '& .MuiTab-root': { - textTransform: 'none', - fontWeight: 500, - minWidth: 'auto', - minHeight: 48, - paddingLeft: 0, - paddingRight: 0, - fontSize: { xs: '0.95rem', sm: '1rem' }, - color: theme.palette.text.secondary, - '&.Mui-selected': { - color: theme.palette.primary.main, - }, - }, - })} - > - - - - - - - + {TABS.map((tab) => ( + + ))} + - {activeTab === 0 && } - {activeTab === 1 && } - {activeTab === 2 && } - {activeTab === 3 && ( - ({ - borderRadius: 3, - border: '1px solid', - borderColor: theme.palette.border.light, - backgroundColor: theme.palette.surface.transparent, - width: '100%', - })} - elevation={0} - > - - - - - )} - {activeTab === 4 && } + {activeTab === 'about' && } + {activeTab === 'getting-started' && } + {activeTab === 'languages' && } + {activeTab === 'faq' && }