diff --git a/public/locales/en/components/existingtab.json b/public/locales/en/components/existingtab.json
new file mode 100644
index 0000000..052818a
--- /dev/null
+++ b/public/locales/en/components/existingtab.json
@@ -0,0 +1,5 @@
+{
+ "company_name":"Company name",
+ "tier":"Gold-tier supporter",
+ "link_text":"Visit website"
+}
\ No newline at end of file
diff --git a/public/locales/en/components/faq.json b/public/locales/en/components/faq.json
new file mode 100644
index 0000000..c875992
--- /dev/null
+++ b/public/locales/en/components/faq.json
@@ -0,0 +1,24 @@
+{
+ "heading": "Frequently asked",
+ "text_part_1": "Got questions? Hit us up",
+ "link_text": "on our Discord",
+ "text_part_2": "or check the most common ones below.",
+ "faqs": [
+ {
+ "question": "What is the minimum monetary amount to start sponsoring you?",
+ "answer": "The bare minimum is one (1) USD per month. So with 12 dollars, you can contribute to this project for a whole year and get listed as a bronze-tier supporter."
+ },
+ {
+ "question": "Why should our company sponsor your project?"
+ },
+ {
+ "question": "Can I down- or upgrade sponsor tiers? How and when?"
+ },
+ {
+ "question": "Do you have any kind of collaboration deals with educational orgs?"
+ },
+ {
+ "question": "What if the project ends - will I be refunded somehow?"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/public/locales/en/components/monthlytab.json b/public/locales/en/components/monthlytab.json
new file mode 100644
index 0000000..84492ab
--- /dev/null
+++ b/public/locales/en/components/monthlytab.json
@@ -0,0 +1,41 @@
+{
+ "buttonLabel": "Talk to us to get started",
+ "tiers": {
+ "bronze": {
+ "title": "Bronze",
+ "price": "Starting at $5 per month",
+ "description": "For individuals or small companies that wish contribute to our project.",
+ "checklist": [
+ "Addition to our sponsors’ list",
+ "Discord badge"
+ ]
+ },
+ "silver": {
+ "title": "Silver",
+ "price": "Starting at $20 per month",
+ "description": "Meant for small-to-medium sized companies.",
+ "checklist": [
+ "Secondary level support via Discord",
+ "A second additional perk"
+ ]
+ },
+ "gold": {
+ "title": "Gold",
+ "price": "Starting at $50 per month",
+ "description": "Meant for medium sized companies.",
+ "checklist": [
+ "First level support via Discord",
+ "A second additional perk"
+ ]
+ },
+ "platinum": {
+ "title": "Platinum",
+ "price": "Starting at $200 per month",
+ "description": "Meant for large companies that want this OSS project to shine.",
+ "checklist": [
+ "Priority support via Discord",
+ "A second additional perk"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/public/locales/en/components/onetimetab.json b/public/locales/en/components/onetimetab.json
new file mode 100644
index 0000000..6464103
--- /dev/null
+++ b/public/locales/en/components/onetimetab.json
@@ -0,0 +1,12 @@
+{
+ "one_time": {
+ "title": "One-time sponsorship",
+ "description": "Want to support is with a singular support payment?",
+ "checklist": [
+ "No monthly/yearly payments",
+ "Discord badge",
+ "Discord badge"
+ ]
+ },
+ "buttonLabel": "Talk to us to get started"
+}
\ No newline at end of file
diff --git a/public/locales/en/components/steps.json b/public/locales/en/components/steps.json
new file mode 100644
index 0000000..e4f20e3
--- /dev/null
+++ b/public/locales/en/components/steps.json
@@ -0,0 +1,22 @@
+{
+ "heading":"Steps to becoming a sponsor",
+ "description":"We prefer to talk directly with each of our potential sponsor as we hope that our core values align and that we are truly a fitting match.",
+ "steps":{
+ "01":{
+ "number":"01",
+ "step":"Send us a message on our Discord server",
+ "step_description":"We are active on our Discord and it’s the best way to stay in touch with us and follow our work.",
+ "link_text":"Join our Discord"
+ },
+ "02":{
+ "number":"02",
+ "step":"Apply as a sponsor by filling out a form",
+ "step_description":"If our preliminary chat goes well according to both you and us, we’ll give you a link to a form that you can fill out."
+ },
+ "03":{
+ "number":"03",
+ "step":"Wait for confirmation (and our huge thanks!)",
+ "step_description":"We will contact you via Discord or email and let you know if everything is in order for the sponsorship."
+ }
+ }
+}
\ No newline at end of file
diff --git a/public/locales/en/pages/sponsors.json b/public/locales/en/pages/sponsors.json
new file mode 100644
index 0000000..96be776
--- /dev/null
+++ b/public/locales/en/pages/sponsors.json
@@ -0,0 +1,11 @@
+{
+ "sponsors":{
+ "header":"Supported by awesome sponsors worldwide",
+ "description":"Our open source project is made possible with the support of awesome organizations and companies."
+ },
+ "segments":{
+ "monthly":"Monthly sponsorship",
+ "one_time":"One-time sponsorship",
+ "existing":"Existing sponsors"
+ }
+}
\ No newline at end of file
diff --git a/src/App.tsx b/src/App.tsx
index 5f04596..19c4f0f 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -16,6 +16,7 @@ import LoginProcessPage from './pages/LoginProcess';
import OurTeamPage from './pages/OurTeam';
import PluginsPage from './pages/Plugins';
import PrivacyPolicy from './pages/PrivacyPolicy';
+import Sponsors from './pages/Sponsors';
import TermsOfService from './pages/TermsOfService';
import ThemesPage from './pages/Themes';
import UserProfilePage from './pages/UserProfile';
@@ -76,6 +77,7 @@ const App: React.FC = () => {
{ element: , path: '/plugins' },
{ element: , path: '/privacy-policy' },
{ element: , path: '/themes' },
+ { element: , path: '/sponsors' },
{ element: , path: '/our-team' },
{ element: , path: '/terms-of-service' },
{
diff --git a/src/assets/images/SponsorsPage/bronze_sponsor.jpg b/src/assets/images/SponsorsPage/bronze_sponsor.jpg
new file mode 100644
index 0000000..4d66841
Binary files /dev/null and b/src/assets/images/SponsorsPage/bronze_sponsor.jpg differ
diff --git a/src/assets/images/SponsorsPage/gold_sponsor.jpg b/src/assets/images/SponsorsPage/gold_sponsor.jpg
new file mode 100644
index 0000000..0fb8f05
Binary files /dev/null and b/src/assets/images/SponsorsPage/gold_sponsor.jpg differ
diff --git a/src/assets/images/SponsorsPage/one_time_sponsor.jpg b/src/assets/images/SponsorsPage/one_time_sponsor.jpg
new file mode 100644
index 0000000..731121d
Binary files /dev/null and b/src/assets/images/SponsorsPage/one_time_sponsor.jpg differ
diff --git a/src/assets/images/SponsorsPage/platinum_sponsor.jpg b/src/assets/images/SponsorsPage/platinum_sponsor.jpg
new file mode 100644
index 0000000..5708c9c
Binary files /dev/null and b/src/assets/images/SponsorsPage/platinum_sponsor.jpg differ
diff --git a/src/assets/images/SponsorsPage/silver_sponsor.jpg b/src/assets/images/SponsorsPage/silver_sponsor.jpg
new file mode 100644
index 0000000..766b1fb
Binary files /dev/null and b/src/assets/images/SponsorsPage/silver_sponsor.jpg differ
diff --git a/src/components/Sponsors/ExistingTab.tsx b/src/components/Sponsors/ExistingTab.tsx
new file mode 100644
index 0000000..87fb3b8
--- /dev/null
+++ b/src/components/Sponsors/ExistingTab.tsx
@@ -0,0 +1,51 @@
+import { Box, Grid, Grid2, Link, Typography } from '@mui/material';
+import { ArrowRight } from 'lucide-react';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+const ExistingTab: React.FC = () => {
+ const { t } = useTranslation('components/existingtab');
+ return (
+
+
+ {Array.from({ length: 12 }).map(() => (
+
+
+
+
+
+
+
+ {t('company_name')}
+
+
+ {t('tier')}
+
+
+
+
+ {t('link_text')}
+
+
+
+
+
+ ))}
+
+
+ );
+};
+
+export default ExistingTab;
diff --git a/src/components/Sponsors/FAQ.tsx b/src/components/Sponsors/FAQ.tsx
new file mode 100644
index 0000000..1de4e77
--- /dev/null
+++ b/src/components/Sponsors/FAQ.tsx
@@ -0,0 +1,52 @@
+import { Accordion, AccordionDetails, AccordionSummary, Box, Link, Typography } from '@mui/material';
+import { ChevronDown } from 'lucide-react';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+type FAQ = {
+ question: string;
+ answer?: string;
+};
+const FAQ: React.FC = () => {
+ const { t } = useTranslation('components/faq');
+ const faqs = t('faqs', { returnObjects: true }) as FAQ[];
+ return (
+
+
+ {t('heading')}
+
+
+ {t('text_part_1')} {t('link_text')} {t('text_part_2')}
+
+
+ {faqs.map((faq) => (
+
+ }>
+ {faq.question}
+
+ {faq.answer ? (
+
+
+ The bare minimum is one (1) USD per month. So with 12 dollars, you can contribute to this project for
+ a whole year and get listed as a bronze-tier supporter.
+
+
+ ) : null}
+
+ ))}
+
+
+ );
+};
+
+export default FAQ;
diff --git a/src/components/Sponsors/MonthlyTab.tsx b/src/components/Sponsors/MonthlyTab.tsx
new file mode 100644
index 0000000..fd48cf6
--- /dev/null
+++ b/src/components/Sponsors/MonthlyTab.tsx
@@ -0,0 +1,130 @@
+import {
+ Box,
+ Button,
+ Card,
+ CardActions,
+ CardContent,
+ CardMedia,
+ Grid,
+ Grid2,
+ List,
+ ListItem,
+ ListItemIcon,
+ Typography,
+} from '@mui/material';
+import { Check } from 'lucide-react';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+import bronzeSponsor from '../../assets/images/SponsorsPage/bronze_sponsor.jpg';
+import goldSponsor from '../../assets/images/SponsorsPage/gold_sponsor.jpg';
+import platinumSponsor from '../../assets/images/SponsorsPage/platinum_sponsor.jpg';
+import silverSponsor from '../../assets/images/SponsorsPage/silver_sponsor.jpg';
+
+type TierItem = {
+ title: string;
+ price: string;
+ description: string;
+ checklist: string[];
+};
+
+type MonthlyTabTranslation = {
+ [tier: string]: TierItem;
+};
+
+const MonthlyTab: React.FC = () => {
+ const { t } = useTranslation('components/monthlytab');
+
+ const monthlyTiers = t('tiers', { returnObjects: true }) as MonthlyTabTranslation;
+
+ const getTierImage = (tier: string) => {
+ switch (tier) {
+ case 'bronze':
+ return bronzeSponsor;
+ case 'silver':
+ return silverSponsor;
+ case 'gold':
+ return goldSponsor;
+ case 'platinum':
+ return platinumSponsor;
+ default:
+ return bronzeSponsor;
+ }
+ };
+ return (
+
+
+ {Object.keys(monthlyTiers).map((tier) => (
+
+
+
+
+
+ {monthlyTiers[tier].title}
+
+
+ {monthlyTiers[tier].price}
+
+
+ {monthlyTiers[tier].description}
+
+
+ {monthlyTiers[tier].checklist.map((checkListItem, index) => (
+
+
+
+
+ {checkListItem}
+
+ ))}
+
+
+
+
+
+
+
+ ))}
+
+
+ );
+};
+
+export default MonthlyTab;
diff --git a/src/components/Sponsors/OneTimeTab.tsx b/src/components/Sponsors/OneTimeTab.tsx
new file mode 100644
index 0000000..11c4cb0
--- /dev/null
+++ b/src/components/Sponsors/OneTimeTab.tsx
@@ -0,0 +1,75 @@
+import {
+ Box,
+ Button,
+ Card,
+ CardActions,
+ CardContent,
+ CardMedia,
+ List,
+ ListItem,
+ ListItemIcon,
+ Typography,
+} from '@mui/material';
+import { Check } from 'lucide-react';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+import oneTimeSponsor from '../../assets/images/SponsorsPage/one_time_sponsor.jpg';
+
+const OneTimeTab: React.FC = () => {
+ const { t } = useTranslation('components/onetimetab');
+ const checklist = t('one_time.checklist', { returnObjects: true }) as Array;
+ return (
+
+
+
+
+
+ {t('one_time.title')}
+
+
+ {t('one_time.description')}
+
+
+ {checklist.map((checkListItem, index) => (
+
+
+
+
+ {checkListItem}
+
+ ))}
+
+
+
+
+
+
+
+ );
+};
+
+export default OneTimeTab;
diff --git a/src/components/Sponsors/Steps.tsx b/src/components/Sponsors/Steps.tsx
new file mode 100644
index 0000000..cc1bffe
--- /dev/null
+++ b/src/components/Sponsors/Steps.tsx
@@ -0,0 +1,118 @@
+import { Box, Grid, Grid2, Link, Typography } from '@mui/material';
+import { ArrowRight } from 'lucide-react';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+import { Endpoints } from '@/constants/Endpoints';
+
+type StepItem = {
+ number: string;
+ step: string;
+ step_description: string;
+ link_text?: string;
+};
+
+type Steps = {
+ [step: string]: StepItem;
+};
+
+const Steps: React.FC = () => {
+ const { t } = useTranslation('components/steps');
+
+ const steps = t('steps', { returnObjects: true }) as Steps;
+
+ const sortedSteps = Object.keys(steps).sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
+
+ return (
+
+
+ {t('heading')}
+
+
+ {t('description')}
+
+
+ {sortedSteps.map((step) => (
+
+
+ {steps[step].number}
+
+
+ {steps[step].step}
+
+
+ {steps[step].step_description}
+
+ {steps[step].link_text ? (
+
+ {steps[step].link_text}
+
+
+ ) : null}
+
+ ))}
+
+
+ );
+};
+
+export default Steps;
diff --git a/src/pages/Sponsors.tsx b/src/pages/Sponsors.tsx
new file mode 100644
index 0000000..038c376
--- /dev/null
+++ b/src/pages/Sponsors.tsx
@@ -0,0 +1,124 @@
+import { Box, Skeleton, Tab, Tabs, Typography } from '@mui/material';
+import React, { lazy, Suspense, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+
+import ExistingTab from '@/components/Sponsors/ExistingTab';
+import FAQ from '@/components/Sponsors/FAQ';
+import MonthlyTab from '@/components/Sponsors/MonthlyTab';
+import OneTimeTab from '@/components/Sponsors/OneTimeTab';
+import Steps from '@/components/Sponsors/Steps';
+
+const Footer = lazy(() => import('@/components/Home/Footer'));
+
+const Sponsors: React.FC = () => {
+ const [currentSponsorCategory, setCurrentSponsoryCategory] = useState(0);
+ const { t } = useTranslation('pages/sponsors');
+
+ const handleCategoryChange = (event: React.SyntheticEvent, value: number) => {
+ setCurrentSponsoryCategory(value);
+ };
+
+ return (
+
+
+ {t('sponsors.header')}
+
+
+ {t('sponsors.description')}
+
+
+
+
+
+
+
+
+ {currentSponsorCategory === 0 ? : null}
+ {currentSponsorCategory === 1 ? : null}
+ {currentSponsorCategory === 2 ? : null}
+
+
+ }>
+
+
+
+ );
+};
+
+export default Sponsors;
diff --git a/src/themes/darkTheme.ts b/src/themes/darkTheme.ts
index ab569a2..f7601e1 100644
--- a/src/themes/darkTheme.ts
+++ b/src/themes/darkTheme.ts
@@ -76,6 +76,7 @@ const darkTheme = createTheme({
secondary: '#A1A1AA',
secondaryBtn: '#FAFAFA',
secondaryBtnHover: '#D1D5DB',
+ tertiary: '#A7AFB8',
},
},
typography: {