38 changed files with 1026 additions and 755 deletions
-
2README.md
-
30web/components/AnimatedScrollWrapper.tsx
-
62web/components/Navbar.tsx
-
43web/components/analytics/Analytics.tsx
-
92web/components/dashboard/ApiKeyList.tsx
-
84web/components/dashboard/DeviceList.tsx
-
17web/components/dashboard/GenerateApiKey.tsx
-
135web/components/dashboard/SendSMS.tsx
-
27web/components/dashboard/UserStats.tsx
-
2web/components/home/CodeSnippetSection.tsx
-
88web/components/home/DownloadAppSection.tsx
-
58web/components/home/FeaturesSection.tsx
-
57web/components/home/HowItWorksSection.tsx
-
124web/components/home/IntroSection.tsx
-
45web/components/landing/CodeSnippetSection.tsx
-
91web/components/landing/DownloadAppSection.tsx
-
64web/components/landing/FeaturesSection.tsx
-
60web/components/landing/HowItWorksSection.tsx
-
153web/components/landing/IntroSection.tsx
-
0web/components/landing/featuresContent.ts
-
0web/components/landing/howItWorksContent.ts
-
6web/lib/httpClient.ts
-
0web/pages/404.tsx
-
4web/pages/_app.tsx
-
23web/pages/dashboard.tsx
-
16web/pages/index.tsx
-
11web/pages/login.tsx
-
16web/pages/register.tsx
-
34web/services/authService.ts
-
34web/services/gatewayService.ts
-
66web/services/index.ts
-
6web/services/types.ts
-
61web/store/apiKeyListReducer.ts
-
85web/store/apiKeySlice.ts
-
22web/store/authSlice.ts
-
61web/store/deviceListReducer.ts
-
92web/store/deviceSlice.ts
-
10web/store/store.ts
@ -0,0 +1,30 @@ |
|||
import React, { ReactNode } from 'react' |
|||
import { motion } from 'framer-motion' |
|||
|
|||
interface AnimatedScrollWrapperProps { |
|||
children: ReactNode |
|||
} |
|||
const AnimatedScrollWrapper = ({ children }: AnimatedScrollWrapperProps) => { |
|||
return ( |
|||
<motion.div |
|||
variants={{ |
|||
hidden: { opacity: 0, y: 10 }, |
|||
visible: { |
|||
opacity: 1, |
|||
y: 0, |
|||
transition: { |
|||
duration: 0.5, |
|||
ease: 'easeInOut', |
|||
// delay: 0.25,
|
|||
}, |
|||
}, |
|||
}} |
|||
initial='hidden' |
|||
whileInView='visible' |
|||
> |
|||
{children} |
|||
</motion.div> |
|||
) |
|||
} |
|||
|
|||
export default AnimatedScrollWrapper |
|||
@ -0,0 +1,43 @@ |
|||
import Script from 'next/script' |
|||
|
|||
const Analytics = () => { |
|||
return ( |
|||
<> |
|||
{/* Global Site Tag (gtag.js) - Google Analytics */} |
|||
<Script |
|||
id='gtag1' |
|||
strategy='afterInteractive' |
|||
src={`https:www.googletagmanager.com/gtag/js?id=G-MLD1JPRQZ`} |
|||
/> |
|||
<Script |
|||
id='gtag2' |
|||
strategy='afterInteractive' |
|||
dangerouslySetInnerHTML={{ |
|||
__html: `
|
|||
window.dataLayer = window.dataLayer || []; |
|||
function gtag(){dataLayer.push(arguments);} |
|||
gtag('js', new Date()); |
|||
gtag('config', 'G-MLD1JPRQZ', { |
|||
page_path: window.location.pathname, |
|||
}); |
|||
`,
|
|||
}} |
|||
/> |
|||
<Script |
|||
id='ms-clarity1' |
|||
strategy='afterInteractive' |
|||
dangerouslySetInnerHTML={{ |
|||
__html: `
|
|||
(function(c,l,a,r,i,t,y){ |
|||
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)}; |
|||
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i; |
|||
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y); |
|||
})(window, document, "clarity", "script", "iacr7j4ozh"); |
|||
`,
|
|||
}} |
|||
/> |
|||
</> |
|||
) |
|||
} |
|||
|
|||
export default Analytics |
|||
@ -1,88 +0,0 @@ |
|||
import { |
|||
Box, |
|||
Button, |
|||
chakra, |
|||
Flex, |
|||
Image, |
|||
useColorModeValue, |
|||
} from '@chakra-ui/react' |
|||
import React from 'react' |
|||
|
|||
export default function DownloadAppSection() { |
|||
return ( |
|||
<Box my={16}> |
|||
<Flex |
|||
padding={5} |
|||
background={useColorModeValue('gray.100', 'gray.700')} |
|||
borderRadius='2xl' |
|||
> |
|||
<Flex |
|||
borderRadius='2xl' |
|||
m={{ base: 5, md: 8 }} |
|||
p={{ base: 5, md: 8 }} |
|||
width='100%' |
|||
border='1px solid gray' |
|||
direction='row' |
|||
justifyContent='center' |
|||
> |
|||
<Box> |
|||
<Image |
|||
alt={'Hero Image'} |
|||
fit={'cover'} |
|||
align={'center'} |
|||
w={'180px'} |
|||
// h={'100%'}
|
|||
src={'/images/smsgatewayandroid.png'} |
|||
/> |
|||
</Box> |
|||
<Box> |
|||
<Flex |
|||
height='100%' |
|||
direction='column' |
|||
justifyContent='center' |
|||
alignItems='center' |
|||
> |
|||
<chakra.h1 |
|||
fontSize='md' |
|||
fontWeight='bold' |
|||
my={4} |
|||
color={useColorModeValue('gray.800', 'white')} |
|||
> |
|||
Download the App to get started! |
|||
</chakra.h1> |
|||
<chakra.p |
|||
fontSize='sm' |
|||
color={useColorModeValue('gray.600', 'gray.400')} |
|||
mb={4} |
|||
> |
|||
Unlock the power of messaging with our open-source Android SMS |
|||
Gateway. |
|||
</chakra.p> |
|||
<a href='/android' target='_blank'> |
|||
<Button |
|||
/* flex={1} */ |
|||
px={4} |
|||
fontSize={'sm'} |
|||
rounded={'full'} |
|||
bg={'blue.400'} |
|||
color={'white'} |
|||
boxShadow={ |
|||
'0px 1px 25px -5px rgb(66 153 225 / 48%), 0 10px 10px -5px rgb(66 153 225 / 43%)' |
|||
} |
|||
_hover={{ |
|||
bg: 'blue.500', |
|||
}} |
|||
_focus={{ |
|||
bg: 'blue.500', |
|||
}} |
|||
> |
|||
Download App |
|||
</Button> |
|||
</a> |
|||
</Flex> |
|||
</Box> |
|||
</Flex> |
|||
</Flex> |
|||
</Box> |
|||
) |
|||
} |
|||
@ -1,58 +0,0 @@ |
|||
import { CheckIcon } from '@chakra-ui/icons' |
|||
import { |
|||
Box, |
|||
Container, |
|||
Heading, |
|||
HStack, |
|||
Icon, |
|||
SimpleGrid, |
|||
Stack, |
|||
Text, |
|||
useColorModeValue, |
|||
VStack, |
|||
} from '@chakra-ui/react' |
|||
import React from 'react' |
|||
import { featuresContent } from './featuresContent' |
|||
|
|||
export default function FeaturesSection() { |
|||
const boxBgColor = useColorModeValue('gray.100', 'gray.800') |
|||
|
|||
return ( |
|||
<Box p={4} my={16} maxW={'6xl'}> |
|||
<Heading fontSize={'3xl'} textAlign={'center'} pb={0}> |
|||
Features |
|||
</Heading> |
|||
<Text color={'gray.600'} fontSize={'lg'} textAlign={'center'}> |
|||
The ultimate solution for your messaging needs! Our free open-source |
|||
Android-based SMS Gateway provides you with all the features you need to |
|||
effectively manage your SMS communications. From sending messages and |
|||
automating messaging workflows via API, our SMS Gateway is the perfect |
|||
tool for any small/mid business or individual. |
|||
</Text> |
|||
|
|||
<Container maxW={'6xl'} mt={0}> |
|||
<SimpleGrid columns={{ base: 1, md: 2, lg: 4 }} spacing={3} pt={16}> |
|||
{featuresContent.map((feature, i) => ( |
|||
<HStack |
|||
key={i} |
|||
align={'top'} |
|||
borderWidth='1px' |
|||
borderRadius='sm' |
|||
p={2} |
|||
shadow='lg' |
|||
background={boxBgColor} |
|||
> |
|||
<Box color={'green.400'} px={1}> |
|||
<Icon as={CheckIcon} /> |
|||
</Box> |
|||
<VStack align={'start'}> |
|||
<Text fontWeight={800}>{feature.title}</Text> |
|||
<Text fontWeight='normal'>{feature.description}</Text> |
|||
</VStack> |
|||
</HStack> |
|||
))} |
|||
</SimpleGrid> |
|||
</Container> |
|||
</Box> |
|||
) |
|||
} |
|||
@ -1,57 +0,0 @@ |
|||
import { AddIcon, MinusIcon } from '@chakra-ui/icons' |
|||
import { |
|||
Accordion, |
|||
AccordionButton, |
|||
AccordionItem, |
|||
AccordionPanel, |
|||
Box, |
|||
Container, |
|||
Heading, |
|||
Text, |
|||
} from '@chakra-ui/react' |
|||
import React from 'react' |
|||
import { howItWorksContent } from './howItWorksContent' |
|||
|
|||
export default function HowItWorksSection() { |
|||
return ( |
|||
<Box px={4} my={24} maxW={'6xl'}> |
|||
{/* @ts-ignore */} |
|||
<a name='how-it-works'> |
|||
<Heading fontSize={'3xl'} textAlign={'center'}> |
|||
How It Works |
|||
</Heading> |
|||
</a> |
|||
<Text color={'gray.600'} fontSize={'lg'} textAlign={'center'}> |
|||
How it works is simple. You install the app on your Android device, and |
|||
it will turn your device into a SMS Gateway. You can then use the API to |
|||
send SMS messages from your own applications. |
|||
</Text> |
|||
|
|||
<Container maxW={'6xl'} mt={10} pt={0}> |
|||
<Accordion allowMultiple defaultIndex={[]}> |
|||
{howItWorksContent.map(({ title, description }) => ( |
|||
<AccordionItem key={title}> |
|||
{({ isExpanded }) => ( |
|||
<> |
|||
<h2> |
|||
<AccordionButton> |
|||
<Box as='span' flex='1' textAlign='left'> |
|||
{title} |
|||
</Box> |
|||
{isExpanded ? ( |
|||
<MinusIcon fontSize='12px' /> |
|||
) : ( |
|||
<AddIcon fontSize='12px' /> |
|||
)} |
|||
</AccordionButton> |
|||
</h2> |
|||
<AccordionPanel pb={4}>{description}</AccordionPanel> |
|||
</> |
|||
)} |
|||
</AccordionItem> |
|||
))} |
|||
</Accordion> |
|||
</Container> |
|||
</Box> |
|||
) |
|||
} |
|||
@ -1,124 +0,0 @@ |
|||
import { |
|||
Container, |
|||
Stack, |
|||
Flex, |
|||
Box, |
|||
Heading, |
|||
Text, |
|||
Button, |
|||
Image, |
|||
createIcon, |
|||
} from '@chakra-ui/react' |
|||
import Link from 'next/link' |
|||
import Router from 'next/router' |
|||
import { selectAuth } from '../../store/authReducer' |
|||
import { useSelector } from 'react-redux' |
|||
import { ChatIcon } from '@chakra-ui/icons' |
|||
|
|||
export default function IntroSection() { |
|||
const { currentUser } = useSelector(selectAuth) |
|||
|
|||
const handleGetStarted = () => { |
|||
if (!currentUser) { |
|||
Router.push('/register') |
|||
} else { |
|||
Router.push('/dashboard') |
|||
} |
|||
} |
|||
|
|||
return ( |
|||
<Container maxW={'7xl'} py={8}> |
|||
<Stack |
|||
align={'center'} |
|||
spacing={{ base: 8, md: 10 }} |
|||
py={{ base: 20, md: 28 }} |
|||
direction={{ base: 'column', md: 'row' }} |
|||
> |
|||
<Stack flex={1} spacing={{ base: 5, md: 10 }}> |
|||
<Heading |
|||
lineHeight={1.1} |
|||
fontWeight={600} |
|||
fontSize={{ base: '3xl', sm: '4xl', lg: '5xl' }} |
|||
> |
|||
<Text as={'span'} position={'relative'} fontWeight={600}> |
|||
<ChatIcon /> Text |
|||
<Text as={'span'} color={'blue.400'} decoration='underline'> |
|||
Bee |
|||
</Text> |
|||
</Text> |
|||
<br /> |
|||
<Text as={'span'} color={'blue.400'} fontWeight={300}> |
|||
Make your android device a portable SMS Gateway! |
|||
</Text> |
|||
</Heading> |
|||
<Text |
|||
color={'gray.500'} |
|||
fontSize={{ base: 'md', sm: 'lg', lg: 'xl' }} |
|||
> |
|||
Unlock the power of messaging with our open-source Android SMS |
|||
Gateway. |
|||
</Text> |
|||
<Stack |
|||
spacing={{ base: 4, sm: 6 }} |
|||
direction={{ base: 'column', sm: 'row' }} |
|||
> |
|||
<Button |
|||
rounded={'full'} |
|||
size={'lg'} |
|||
fontWeight={'normal'} |
|||
px={6} |
|||
colorScheme={'blue'} |
|||
bg={'blue.400'} |
|||
_hover={{ bg: 'blue.500' }} |
|||
onClick={handleGetStarted} |
|||
> |
|||
Get Started |
|||
</Button> |
|||
<Link href={'#how-it-works'} passHref> |
|||
<Button |
|||
rounded={'full'} |
|||
size={'lg'} |
|||
fontWeight={'normal'} |
|||
px={6} |
|||
leftIcon={<PlayIcon h={4} w={4} color={'gray.300'} />} |
|||
> |
|||
How It Works |
|||
</Button> |
|||
</Link> |
|||
</Stack> |
|||
</Stack> |
|||
<Flex |
|||
flex={1} |
|||
justify={'center'} |
|||
align={'center'} |
|||
position={'relative'} |
|||
w={'full'} |
|||
> |
|||
<Box |
|||
position={'relative'} |
|||
height={'400px'} |
|||
rounded={'2xl'} |
|||
boxShadow={'xs'} |
|||
width={'full'} |
|||
overflow={'hidden'} |
|||
> |
|||
<Image |
|||
alt={'Hero Image'} |
|||
fit={'cover'} |
|||
align={'center'} |
|||
// w={'100%'}
|
|||
// h={'100%'}
|
|||
src={'/images/smsgatewayandroid.png'} |
|||
/> |
|||
</Box> |
|||
</Flex> |
|||
</Stack> |
|||
</Container> |
|||
) |
|||
} |
|||
|
|||
const PlayIcon = createIcon({ |
|||
displayName: 'PlayIcon', |
|||
viewBox: '0 0 58 58', |
|||
d: 'M28.9999 0.562988C13.3196 0.562988 0.562378 13.3202 0.562378 29.0005C0.562378 44.6808 13.3196 57.438 28.9999 57.438C44.6801 57.438 57.4374 44.6808 57.4374 29.0005C57.4374 13.3202 44.6801 0.562988 28.9999 0.562988ZM39.2223 30.272L23.5749 39.7247C23.3506 39.8591 23.0946 39.9314 22.8332 39.9342C22.5717 39.9369 22.3142 39.8701 22.0871 39.7406C21.86 39.611 21.6715 39.4234 21.5408 39.1969C21.4102 38.9705 21.3421 38.7133 21.3436 38.4519V19.5491C21.3421 19.2877 21.4102 19.0305 21.5408 18.8041C21.6715 18.5776 21.86 18.3899 22.0871 18.2604C22.3142 18.1308 22.5717 18.064 22.8332 18.0668C23.0946 18.0696 23.3506 18.1419 23.5749 18.2763L39.2223 27.729C39.4404 27.8619 39.6207 28.0486 39.7458 28.2713C39.8709 28.494 39.9366 28.7451 39.9366 29.0005C39.9366 29.2559 39.8709 29.507 39.7458 29.7297C39.6207 29.9523 39.4404 30.1391 39.2223 30.272Z', |
|||
}) |
|||
@ -0,0 +1,45 @@ |
|||
import { Box, Flex, Heading, Image, Text } from '@chakra-ui/react' |
|||
import React from 'react' |
|||
import AnimatedScrollWrapper from '../AnimatedScrollWrapper' |
|||
|
|||
export default function CodeSnippetSection() { |
|||
return ( |
|||
<AnimatedScrollWrapper> |
|||
<Box m={{ base: 0, md: 8 }} p={{ base: 0, md: 8 }}> |
|||
<Flex |
|||
height='100%' |
|||
direction='column' |
|||
justifyContent='center' |
|||
alignItems='center' |
|||
> |
|||
<Heading fontSize={'3xl'} textAlign={'center'} py={8}> |
|||
Code Snippet |
|||
</Heading> |
|||
<Text color={'gray.600'} fontSize={'lg'} textAlign={'center'} pb='4'> |
|||
Send SMS messages from your web application using our REST API. You |
|||
can use any programming language to interact with our API. Here is a |
|||
sample code snippet in JavaScript using axios library. |
|||
</Text> |
|||
|
|||
<Box |
|||
borderRadius={'lg'} |
|||
padding={{ base: 0, md: 8 }} |
|||
border={'1px solid #E2E8F0'} |
|||
w={{ base: '100%', md: '70%' }} |
|||
> |
|||
<Image |
|||
alt={'Hero Image'} |
|||
fit={'cover'} |
|||
align={'center'} |
|||
// h={'100%'}
|
|||
src={ |
|||
'https://ik.imagekit.io/vernu/textbee/Screenshot_2023-06-18_at_11.30.25_AM.png?updatedAt=1687077054749' |
|||
} |
|||
borderRadius={'lg'} |
|||
/> |
|||
</Box> |
|||
</Flex> |
|||
</Box> |
|||
</AnimatedScrollWrapper> |
|||
) |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
import { |
|||
Box, |
|||
Button, |
|||
chakra, |
|||
Flex, |
|||
Image, |
|||
useColorModeValue, |
|||
} from '@chakra-ui/react' |
|||
import React from 'react' |
|||
import AnimatedScrollWrapper from '../AnimatedScrollWrapper' |
|||
|
|||
export default function DownloadAppSection() { |
|||
return ( |
|||
<AnimatedScrollWrapper> |
|||
<Box my={16}> |
|||
<Flex |
|||
padding={5} |
|||
background={useColorModeValue('gray.100', 'gray.700')} |
|||
borderRadius='2xl' |
|||
> |
|||
<Flex |
|||
borderRadius='2xl' |
|||
m={{ base: 5, md: 8 }} |
|||
p={{ base: 5, md: 8 }} |
|||
width='100%' |
|||
border='1px solid gray' |
|||
direction='row' |
|||
justifyContent='center' |
|||
> |
|||
<Box> |
|||
<Image |
|||
alt={'Hero Image'} |
|||
fit={'cover'} |
|||
align={'center'} |
|||
w={'180px'} |
|||
// h={'100%'}
|
|||
src={'/images/smsgatewayandroid.png'} |
|||
/> |
|||
</Box> |
|||
<Box> |
|||
<Flex |
|||
height='100%' |
|||
direction='column' |
|||
justifyContent='center' |
|||
alignItems='center' |
|||
> |
|||
<chakra.h1 |
|||
fontSize='md' |
|||
fontWeight='bold' |
|||
my={4} |
|||
color={useColorModeValue('gray.800', 'white')} |
|||
> |
|||
Download the App to get started! |
|||
</chakra.h1> |
|||
<chakra.p |
|||
fontSize='sm' |
|||
color={useColorModeValue('gray.600', 'gray.400')} |
|||
mb={4} |
|||
> |
|||
Unlock the power of messaging with our open-source Android SMS |
|||
Gateway. |
|||
</chakra.p> |
|||
<a href='/android' target='_blank'> |
|||
<Button |
|||
/* flex={1} */ |
|||
px={4} |
|||
fontSize={'sm'} |
|||
rounded={'full'} |
|||
bg={'blue.400'} |
|||
color={'white'} |
|||
boxShadow={ |
|||
'0px 1px 25px -5px rgb(66 153 225 / 48%), 0 10px 10px -5px rgb(66 153 225 / 43%)' |
|||
} |
|||
_hover={{ |
|||
bg: 'blue.500', |
|||
}} |
|||
_focus={{ |
|||
bg: 'blue.500', |
|||
}} |
|||
> |
|||
Download App |
|||
</Button> |
|||
</a> |
|||
</Flex> |
|||
</Box> |
|||
</Flex> |
|||
</Flex> |
|||
</Box> |
|||
</AnimatedScrollWrapper> |
|||
) |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
import { CheckIcon } from '@chakra-ui/icons' |
|||
import { |
|||
Box, |
|||
Container, |
|||
Heading, |
|||
HStack, |
|||
Icon, |
|||
SimpleGrid, |
|||
Text, |
|||
useColorModeValue, |
|||
VStack, |
|||
} from '@chakra-ui/react' |
|||
import React from 'react' |
|||
import { featuresContent } from './featuresContent' |
|||
import AnimatedScrollWrapper from '../AnimatedScrollWrapper' |
|||
|
|||
const FeatureCard = ({ feature }) => { |
|||
const boxBgColor = useColorModeValue('gray.100', 'gray.800') |
|||
return ( |
|||
<HStack |
|||
align={'top'} |
|||
borderWidth='1px' |
|||
borderRadius='sm' |
|||
p={2} |
|||
shadow='lg' |
|||
background={boxBgColor} |
|||
> |
|||
<Box color={'green.400'} px={1}> |
|||
<Icon as={CheckIcon} /> |
|||
</Box> |
|||
<VStack align={'start'}> |
|||
<Text fontWeight={800}>{feature.title}</Text> |
|||
<Text fontWeight='normal'>{feature.description}</Text> |
|||
</VStack> |
|||
</HStack> |
|||
) |
|||
} |
|||
|
|||
export default function FeaturesSection() { |
|||
return ( |
|||
<AnimatedScrollWrapper> |
|||
<Box p={4} my={16} maxW={'6xl'}> |
|||
<Heading fontSize={'3xl'} textAlign={'center'} pb={0}> |
|||
Features |
|||
</Heading> |
|||
<Text color={'gray.600'} fontSize={'lg'} textAlign={'center'}> |
|||
The ultimate solution for your messaging needs! Our free open-source |
|||
Android-based SMS Gateway provides you with all the features you need |
|||
to effectively manage your SMS communications. From sending messages |
|||
and automating messaging workflows via API, our SMS Gateway is the |
|||
perfect tool for any small/mid business or individual. |
|||
</Text> |
|||
|
|||
<Container maxW={'6xl'} mt={0}> |
|||
<SimpleGrid columns={{ base: 1, md: 2, lg: 4 }} spacing={3} pt={16}> |
|||
{featuresContent.map((feature, i) => ( |
|||
<FeatureCard key={feature.title} feature={feature} /> |
|||
))} |
|||
</SimpleGrid> |
|||
</Container> |
|||
</Box> |
|||
</AnimatedScrollWrapper> |
|||
) |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
import { AddIcon, MinusIcon } from '@chakra-ui/icons' |
|||
import { |
|||
Accordion, |
|||
AccordionButton, |
|||
AccordionItem, |
|||
AccordionPanel, |
|||
Box, |
|||
Container, |
|||
Heading, |
|||
Text, |
|||
} from '@chakra-ui/react' |
|||
import React from 'react' |
|||
import { howItWorksContent } from './howItWorksContent' |
|||
import AnimatedScrollWrapper from '../AnimatedScrollWrapper' |
|||
|
|||
export default function HowItWorksSection() { |
|||
return ( |
|||
<AnimatedScrollWrapper> |
|||
<Box px={4} my={24} maxW={'6xl'}> |
|||
{/* @ts-ignore */} |
|||
<a name='how-it-works'> |
|||
<Heading fontSize={'3xl'} textAlign={'center'}> |
|||
How It Works |
|||
</Heading> |
|||
</a> |
|||
<Text color={'gray.600'} fontSize={'lg'} textAlign={'center'}> |
|||
How it works is simple. You install the app on your Android device, |
|||
and it will turn your device into a SMS Gateway. You can then use the |
|||
API to send SMS messages from your own applications. |
|||
</Text> |
|||
|
|||
<Container maxW={'6xl'} mt={10} pt={0}> |
|||
<Accordion allowMultiple defaultIndex={[]}> |
|||
{howItWorksContent.map(({ title, description }) => ( |
|||
<AccordionItem key={title}> |
|||
{({ isExpanded }) => ( |
|||
<> |
|||
<h2> |
|||
<AccordionButton> |
|||
<Box as='span' flex='1' textAlign='left'> |
|||
{title} |
|||
</Box> |
|||
{isExpanded ? ( |
|||
<MinusIcon fontSize='12px' /> |
|||
) : ( |
|||
<AddIcon fontSize='12px' /> |
|||
)} |
|||
</AccordionButton> |
|||
</h2> |
|||
<AccordionPanel pb={4}>{description}</AccordionPanel> |
|||
</> |
|||
)} |
|||
</AccordionItem> |
|||
))} |
|||
</Accordion> |
|||
</Container> |
|||
</Box> |
|||
</AnimatedScrollWrapper> |
|||
) |
|||
} |
|||
@ -0,0 +1,153 @@ |
|||
import { |
|||
Container, |
|||
Stack, |
|||
Flex, |
|||
Box, |
|||
Heading, |
|||
Text, |
|||
Button, |
|||
Image, |
|||
createIcon, |
|||
} from '@chakra-ui/react' |
|||
import Link from 'next/link' |
|||
import Router from 'next/router' |
|||
import { selectAuthUser } from '../../store/authSlice' |
|||
import { useSelector } from 'react-redux' |
|||
import { ChatIcon } from '@chakra-ui/icons' |
|||
import AnimatedScrollWrapper from '../AnimatedScrollWrapper' |
|||
import { motion } from 'framer-motion' |
|||
|
|||
const AnimatedScreenshotImage = () => { |
|||
const animateVariants = { |
|||
hidden: { |
|||
opacity: 0, |
|||
y: Math.floor(Math.random() * 100) + -50, |
|||
x: Math.floor(Math.random() * 100) + 50, |
|||
}, |
|||
visible: { |
|||
opacity: 1, |
|||
y: 0, |
|||
x: 0, |
|||
transition: { |
|||
duration: 2.0, |
|||
}, |
|||
}, |
|||
} |
|||
return ( |
|||
<motion.div |
|||
variants={animateVariants} |
|||
initial='hidden' |
|||
whileInView='visible' |
|||
> |
|||
<Box |
|||
position={'relative'} |
|||
height={'400px'} |
|||
rounded={'2xl'} |
|||
boxShadow={'xs'} |
|||
width={'full'} |
|||
overflow={'hidden'} |
|||
> |
|||
<Image |
|||
alt={'TextBee App Screenshot'} |
|||
fit={'cover'} |
|||
align={'center'} |
|||
src={'/images/smsgatewayandroid.png'} |
|||
/> |
|||
</Box> |
|||
</motion.div> |
|||
) |
|||
} |
|||
|
|||
export default function IntroSection() { |
|||
const authUser = useSelector(selectAuthUser) |
|||
|
|||
const handleGetStarted = () => { |
|||
if (!authUser) { |
|||
Router.push('/register') |
|||
} else { |
|||
Router.push('/dashboard') |
|||
} |
|||
} |
|||
|
|||
return ( |
|||
<AnimatedScrollWrapper> |
|||
<Container maxW={'7xl'} py={8}> |
|||
<Stack |
|||
align={'center'} |
|||
spacing={{ base: 8, md: 10 }} |
|||
py={{ base: 20, md: 28 }} |
|||
direction={{ base: 'column', md: 'row' }} |
|||
> |
|||
<Stack flex={1} spacing={{ base: 5, md: 10 }}> |
|||
<Heading |
|||
lineHeight={1.1} |
|||
fontWeight={600} |
|||
fontSize={{ base: '3xl', sm: '4xl', lg: '5xl' }} |
|||
> |
|||
<Text as={'span'} position={'relative'} fontWeight={600}> |
|||
<ChatIcon /> Text |
|||
<Text as={'span'} color={'blue.400'} decoration='underline'> |
|||
Bee |
|||
</Text> |
|||
</Text> |
|||
<br /> |
|||
<Text as={'span'} color={'blue.400'} fontWeight={300}> |
|||
Make your android device a portable SMS Gateway! |
|||
</Text> |
|||
</Heading> |
|||
<Text |
|||
color={'gray.500'} |
|||
fontSize={{ base: 'md', sm: 'lg', lg: 'xl' }} |
|||
> |
|||
Unlock the power of messaging with our open-source Android SMS |
|||
Gateway. |
|||
</Text> |
|||
<Stack |
|||
spacing={{ base: 4, sm: 6 }} |
|||
direction={{ base: 'column', sm: 'row' }} |
|||
> |
|||
<Button |
|||
rounded={'full'} |
|||
size={'lg'} |
|||
fontWeight={'normal'} |
|||
px={6} |
|||
colorScheme={'blue'} |
|||
bg={'blue.400'} |
|||
_hover={{ bg: 'blue.500' }} |
|||
onClick={handleGetStarted} |
|||
> |
|||
Get Started |
|||
</Button> |
|||
<Link href={'#how-it-works'} passHref> |
|||
<Button |
|||
rounded={'full'} |
|||
size={'lg'} |
|||
fontWeight={'normal'} |
|||
px={6} |
|||
leftIcon={<PlayIcon h={4} w={4} color={'gray.300'} />} |
|||
> |
|||
How It Works |
|||
</Button> |
|||
</Link> |
|||
</Stack> |
|||
</Stack> |
|||
<Flex |
|||
flex={1} |
|||
justify={'center'} |
|||
align={'center'} |
|||
position={'relative'} |
|||
w={'full'} |
|||
> |
|||
<AnimatedScreenshotImage /> |
|||
</Flex> |
|||
</Stack> |
|||
</Container> |
|||
</AnimatedScrollWrapper> |
|||
) |
|||
} |
|||
|
|||
const PlayIcon = createIcon({ |
|||
displayName: 'PlayIcon', |
|||
viewBox: '0 0 58 58', |
|||
d: 'M28.9999 0.562988C13.3196 0.562988 0.562378 13.3202 0.562378 29.0005C0.562378 44.6808 13.3196 57.438 28.9999 57.438C44.6801 57.438 57.4374 44.6808 57.4374 29.0005C57.4374 13.3202 44.6801 0.562988 28.9999 0.562988ZM39.2223 30.272L23.5749 39.7247C23.3506 39.8591 23.0946 39.9314 22.8332 39.9342C22.5717 39.9369 22.3142 39.8701 22.0871 39.7406C21.86 39.611 21.6715 39.4234 21.5408 39.1969C21.4102 38.9705 21.3421 38.7133 21.3436 38.4519V19.5491C21.3421 19.2877 21.4102 19.0305 21.5408 18.8041C21.6715 18.5776 21.86 18.3899 22.0871 18.2604C22.3142 18.1308 22.5717 18.064 22.8332 18.0668C23.0946 18.0696 23.3506 18.1419 23.5749 18.2763L39.2223 27.729C39.4404 27.8619 39.6207 28.0486 39.7458 28.2713C39.8709 28.494 39.9366 28.7451 39.9366 29.0005C39.9366 29.2559 39.8709 29.507 39.7458 29.7297C39.6207 29.9523 39.4404 30.1391 39.2223 30.272Z', |
|||
}) |
|||
@ -0,0 +1,34 @@ |
|||
import httpClient from '../lib/httpClient' |
|||
import { |
|||
GoogleLoginRequestPayload, |
|||
LoginRequestPayload, |
|||
LoginResponse, |
|||
RegisterRequestPayload, |
|||
RegisterResponse, |
|||
} from './types' |
|||
|
|||
class AuthService { |
|||
async login(payload: LoginRequestPayload): Promise<LoginResponse> { |
|||
const res = await httpClient.post(`/auth/login`, payload) |
|||
return res.data.data |
|||
} |
|||
|
|||
async loginWithGoogle( |
|||
payload: GoogleLoginRequestPayload |
|||
): Promise<LoginResponse> { |
|||
const res = await httpClient.post(`/auth/google-login`, payload) |
|||
return res.data.data |
|||
} |
|||
|
|||
async register(payload: RegisterRequestPayload): Promise<RegisterResponse> { |
|||
const res = await httpClient.post(`/auth/register`, payload) |
|||
return res.data.data |
|||
} |
|||
|
|||
async getCurrentUser() { |
|||
const res = await httpClient.get(`/auth/who-am-i`) |
|||
return res.data.data |
|||
} |
|||
} |
|||
|
|||
export const authService = new AuthService() |
|||
@ -0,0 +1,34 @@ |
|||
import httpClient from '../lib/httpClient' |
|||
import { SendSMSRequestPayload } from './types' |
|||
|
|||
class GatewayService { |
|||
async generateApiKey() { |
|||
const res = await httpClient.post(`/auth/api-keys`, {}) |
|||
return res.data.data |
|||
} |
|||
|
|||
async getApiKeyList() { |
|||
const res = await httpClient.get(`/auth/api-keys`) |
|||
return res.data.data |
|||
} |
|||
|
|||
async deleteApiKey(id: string) { |
|||
const res = await httpClient.delete(`/auth/api-keys/${id}`) |
|||
return res.data.data |
|||
} |
|||
|
|||
async getDeviceList() { |
|||
const res = await httpClient.get(`/gateway/devices`) |
|||
return res.data.data |
|||
} |
|||
|
|||
async sendSMS(deviceId: string, payload: SendSMSRequestPayload) { |
|||
const res = await httpClient.post( |
|||
`/gateway/devices/${deviceId}/sendSMS`, |
|||
payload |
|||
) |
|||
return res.data.data |
|||
} |
|||
} |
|||
|
|||
export const gatewayService = new GatewayService() |
|||
@ -1,66 +0,0 @@ |
|||
import axios from '../lib/customAxios' |
|||
import { |
|||
GoogleLoginRequestPayload, |
|||
LoginRequestPayload, |
|||
LoginResponse, |
|||
RegisterRequestPayload, |
|||
RegisterResponse, |
|||
SendSMSRequestPayload, |
|||
} from './types' |
|||
|
|||
export const loginRequest = async ( |
|||
payload: LoginRequestPayload |
|||
): Promise<LoginResponse> => { |
|||
const res = await axios.post(`/auth/login`, payload) |
|||
return res.data.data |
|||
} |
|||
|
|||
export const loginWithGoogleRequest = async ( |
|||
payload: GoogleLoginRequestPayload |
|||
): Promise<LoginResponse> => { |
|||
const res = await axios.post(`/auth/google-login`, payload) |
|||
return res.data.data |
|||
} |
|||
|
|||
export const registerRequest = async ( |
|||
payload: RegisterRequestPayload |
|||
): Promise<RegisterResponse> => { |
|||
const res = await axios.post(`/auth/register`, payload) |
|||
return res.data.data |
|||
} |
|||
|
|||
export const getCurrentUserRequest = async () => { |
|||
const res = await axios.get(`/auth/who-am-i`) |
|||
return res.data.data |
|||
} |
|||
|
|||
export const generateApiKeyRequest = async () => { |
|||
const res = await axios.post(`/auth/api-keys`, {}) |
|||
return res.data.data |
|||
} |
|||
|
|||
export const getApiKeyListRequest = async () => { |
|||
const res = await axios.get(`/auth/api-keys`) |
|||
return res.data.data |
|||
} |
|||
|
|||
export const deleteApiKeyRequest = async (id: string) => { |
|||
const res = await axios.delete(`/auth/api-keys/${id}`) |
|||
return res.data.data |
|||
} |
|||
|
|||
export const getDeviceListRequest = async () => { |
|||
const res = await axios.get(`/gateway/devices`) |
|||
return res.data.data |
|||
} |
|||
|
|||
export const sendSMSRequest = async ( |
|||
deviceId: string, |
|||
payload: SendSMSRequestPayload |
|||
) => { |
|||
const res = await axios.post( |
|||
`/gateway/devices/${deviceId}/sendSMS`, |
|||
payload |
|||
) |
|||
return res.data.data |
|||
} |
|||
@ -1,61 +0,0 @@ |
|||
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' |
|||
import type { PayloadAction } from '@reduxjs/toolkit' |
|||
import { getApiKeyListRequest } from '../services' |
|||
import { createStandaloneToast } from '@chakra-ui/react' |
|||
import { RootState } from './store' |
|||
|
|||
const toast = createStandaloneToast() |
|||
|
|||
const initialState = { |
|||
loading: false, |
|||
data: [], |
|||
} |
|||
|
|||
export const fetchApiKeyList = createAsyncThunk( |
|||
'apiKeyList/fetchApiKeys', |
|||
async (payload, thunkAPI) => { |
|||
try { |
|||
const res = await getApiKeyListRequest() |
|||
return res |
|||
} catch (e) { |
|||
toast({ |
|||
title: e.response.data.error || 'Failed to Fetch apiKeys', |
|||
status: 'error', |
|||
}) |
|||
return thunkAPI.rejectWithValue(e.response.data) |
|||
} |
|||
} |
|||
) |
|||
|
|||
export const apiKeyListSlice = createSlice({ |
|||
name: 'apiKeyList', |
|||
initialState, |
|||
reducers: { |
|||
clearApiKeyList: (state) => { |
|||
state.loading = false |
|||
state.data = [] |
|||
}, |
|||
}, |
|||
extraReducers: (builder) => { |
|||
builder |
|||
.addCase(fetchApiKeyList.pending, (state) => { |
|||
state.loading = true |
|||
}) |
|||
.addCase( |
|||
fetchApiKeyList.fulfilled, |
|||
(state, action: PayloadAction<any>) => { |
|||
state.loading = false |
|||
state.data = action.payload |
|||
} |
|||
) |
|||
.addCase(fetchApiKeyList.rejected, (state) => { |
|||
state.loading = false |
|||
}) |
|||
}, |
|||
}) |
|||
|
|||
export const { clearApiKeyList } = apiKeyListSlice.actions |
|||
|
|||
export const selectApiKeyList = (state: RootState) => state.apiKeyList |
|||
|
|||
export default apiKeyListSlice.reducer |
|||
@ -0,0 +1,85 @@ |
|||
import { createAsyncThunk, createSlice, isAnyOf } from '@reduxjs/toolkit' |
|||
import type { PayloadAction } from '@reduxjs/toolkit' |
|||
import { createStandaloneToast } from '@chakra-ui/react' |
|||
import { RootState } from './store' |
|||
import { gatewayService } from '../services/gatewayService' |
|||
|
|||
const toast = createStandaloneToast() |
|||
|
|||
const initialState = { |
|||
loading: false, |
|||
item: null, |
|||
list: [], |
|||
} |
|||
|
|||
export const fetchApiKeys = createAsyncThunk( |
|||
'apiKey/fetchApiKeys', |
|||
async (payload, { rejectWithValue }) => { |
|||
try { |
|||
const res = await gatewayService.getApiKeyList() |
|||
return res |
|||
} catch (e) { |
|||
toast({ |
|||
title: e.response.data.error || 'Failed to Fetch apiKeys', |
|||
status: 'error', |
|||
}) |
|||
return rejectWithValue(e.response.data) |
|||
} |
|||
} |
|||
) |
|||
|
|||
export const deleteApiKey = createAsyncThunk( |
|||
'apiKey/deleteApiKey', |
|||
async (apiKeyId: string, { dispatch, rejectWithValue }) => { |
|||
try { |
|||
const res = await gatewayService.deleteApiKey(apiKeyId) |
|||
dispatch(fetchApiKeys()) |
|||
toast({ |
|||
title: 'ApiKey deleted successfully', |
|||
status: 'success', |
|||
}) |
|||
return res |
|||
} catch (e) { |
|||
toast({ |
|||
title: e.response.data.error || 'Failed to delete ApiKey', |
|||
status: 'error', |
|||
}) |
|||
return rejectWithValue(e.response.data) |
|||
} |
|||
} |
|||
) |
|||
|
|||
export const apiKeySlice = createSlice({ |
|||
name: 'apiKey', |
|||
initialState, |
|||
reducers: { |
|||
clearApiKeyList: (state) => { |
|||
state.loading = false |
|||
state.list = [] |
|||
}, |
|||
}, |
|||
extraReducers: (builder) => { |
|||
builder |
|||
.addCase(fetchApiKeys.fulfilled, (state, action: PayloadAction<any>) => { |
|||
state.loading = false |
|||
state.list = action.payload |
|||
}) |
|||
.addCase(fetchApiKeys.rejected, (state) => { |
|||
state.loading = false |
|||
}) |
|||
.addMatcher( |
|||
isAnyOf(fetchApiKeys.pending, deleteApiKey.pending), |
|||
(state) => { |
|||
state.loading = true |
|||
} |
|||
) |
|||
}, |
|||
}) |
|||
|
|||
export const { clearApiKeyList } = apiKeySlice.actions |
|||
|
|||
export const selectApiKeyLoading = (state: RootState) => state.apiKey.loading |
|||
export const selectApiKeyList = (state: RootState) => state.apiKey.list |
|||
export const selectApiKeyItem = (state: RootState) => state.apiKey.item |
|||
|
|||
export default apiKeySlice.reducer |
|||
@ -1,61 +0,0 @@ |
|||
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' |
|||
import type { PayloadAction } from '@reduxjs/toolkit' |
|||
import { getDeviceListRequest } from '../services' |
|||
import { createStandaloneToast } from '@chakra-ui/react' |
|||
import { RootState } from './store' |
|||
|
|||
const toast = createStandaloneToast() |
|||
|
|||
const initialState = { |
|||
loading: false, |
|||
data: [], |
|||
} |
|||
|
|||
export const fetchDeviceList = createAsyncThunk( |
|||
'deviceList/fetchDevices', |
|||
async (payload, thunkAPI) => { |
|||
try { |
|||
const res = await getDeviceListRequest() |
|||
return res |
|||
} catch (e) { |
|||
toast({ |
|||
title: e.response.data.error || 'Failed to Fetch devices', |
|||
status: 'error', |
|||
}) |
|||
return thunkAPI.rejectWithValue(e.response.data) |
|||
} |
|||
} |
|||
) |
|||
|
|||
export const deviceListSlice = createSlice({ |
|||
name: 'deviceList', |
|||
initialState, |
|||
reducers: { |
|||
clearDeviceList: (state) => { |
|||
state.loading = false |
|||
state.data = [] |
|||
}, |
|||
}, |
|||
extraReducers: (builder) => { |
|||
builder |
|||
.addCase(fetchDeviceList.pending, (state) => { |
|||
state.loading = true |
|||
}) |
|||
.addCase( |
|||
fetchDeviceList.fulfilled, |
|||
(state, action: PayloadAction<any>) => { |
|||
state.loading = false |
|||
state.data = action.payload |
|||
} |
|||
) |
|||
.addCase(fetchDeviceList.rejected, (state) => { |
|||
state.loading = false |
|||
}) |
|||
}, |
|||
}) |
|||
|
|||
export const { clearDeviceList } = deviceListSlice.actions |
|||
|
|||
export const selectDeviceList = (state: RootState) => state.deviceList |
|||
|
|||
export default deviceListSlice.reducer |
|||
@ -0,0 +1,92 @@ |
|||
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' |
|||
import type { PayloadAction } from '@reduxjs/toolkit' |
|||
import { createStandaloneToast } from '@chakra-ui/react' |
|||
import { RootState } from './store' |
|||
import { gatewayService } from '../services/gatewayService' |
|||
|
|||
const toast = createStandaloneToast() |
|||
|
|||
const initialState = { |
|||
loading: false, |
|||
item: null, |
|||
list: [], |
|||
sendingSMS: false, |
|||
} |
|||
|
|||
export const fetchDevices = createAsyncThunk( |
|||
'device/fetchDevices', |
|||
async (payload, { rejectWithValue }) => { |
|||
try { |
|||
const res = await gatewayService.getDeviceList() |
|||
return res |
|||
} catch (e) { |
|||
toast({ |
|||
title: e.response.data.error || 'Failed to Fetch devices', |
|||
status: 'error', |
|||
}) |
|||
return rejectWithValue(e.response.data) |
|||
} |
|||
} |
|||
) |
|||
|
|||
export const sendSMS = createAsyncThunk( |
|||
'device/sendSMS', |
|||
async ({ deviceId, payload }: any, { rejectWithValue }) => { |
|||
try { |
|||
const res = await gatewayService.sendSMS(deviceId, payload) |
|||
toast({ |
|||
title: 'SMS sent successfully', |
|||
status: 'success', |
|||
}) |
|||
return res |
|||
} catch (e) { |
|||
toast({ |
|||
title: e.response.data.error || 'Failed to send SMS', |
|||
status: 'error', |
|||
}) |
|||
return rejectWithValue(e.response.data) |
|||
} |
|||
} |
|||
) |
|||
|
|||
export const deviceSlice = createSlice({ |
|||
name: 'device', |
|||
initialState, |
|||
reducers: { |
|||
clearDeviceList: (state) => { |
|||
state.loading = false |
|||
state.list = [] |
|||
}, |
|||
}, |
|||
extraReducers: (builder) => { |
|||
builder |
|||
.addCase(fetchDevices.pending, (state) => { |
|||
state.loading = true |
|||
}) |
|||
.addCase(fetchDevices.fulfilled, (state, action: PayloadAction<any>) => { |
|||
state.loading = false |
|||
state.list = action.payload |
|||
}) |
|||
.addCase(fetchDevices.rejected, (state) => { |
|||
state.loading = false |
|||
}) |
|||
.addCase(sendSMS.pending, (state) => { |
|||
state.sendingSMS = true |
|||
}) |
|||
.addCase(sendSMS.fulfilled, (state) => { |
|||
state.sendingSMS = false |
|||
}) |
|||
.addCase(sendSMS.rejected, (state) => { |
|||
state.sendingSMS = false |
|||
}) |
|||
}, |
|||
}) |
|||
|
|||
export const { clearDeviceList } = deviceSlice.actions |
|||
|
|||
export const selectDeviceList = (state: RootState) => state.device.list |
|||
export const selectDeviceItem = (state: RootState) => state.device.item |
|||
export const selectDeviceLoading = (state: RootState) => state.device.loading |
|||
export const selectSendingSMS = (state: RootState) => state.device.sendingSMS |
|||
|
|||
export default deviceSlice.reducer |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue