Browse Source

Merge pull request #1 from vernu/v2

V2
pull/4/head v2.0.0
Israel Abebe 3 years ago
committed by GitHub
parent
commit
a8e3466ef2
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 41
      README.md
  2. 2
      android/.idea/.name
  3. 4
      android/app/build.gradle
  4. 10
      android/app/src/main/java/com/vernu/sms/activities/MainActivity.java
  5. 2
      android/app/src/main/res/layout/activity_main.xml
  6. 8
      android/app/src/main/res/values-night/themes.xml
  7. 2
      android/app/src/main/res/values/strings.xml
  8. 8
      android/app/src/main/res/values/themes.xml
  9. 2
      android/settings.gradle
  10. 4
      api/src/main.ts
  11. 2
      web/.env.example
  12. 52
      web/components/Footer.tsx
  13. 17
      web/components/Navbar.tsx
  14. 11
      web/components/dashboard/DeviceList.tsx
  15. 19
      web/components/dashboard/GenerateApiKey.tsx
  16. 7
      web/components/dashboard/SendSMS.tsx
  17. 16
      web/components/dashboard/UserStats.tsx
  18. 5
      web/components/dashboard/UserStatsCard.tsx
  19. 42
      web/components/home/CodeSnippetSection.tsx
  20. 88
      web/components/home/DownloadAppSection.tsx
  21. 39
      web/components/home/FeaturesSection.tsx
  22. 22
      web/components/home/HowItWorksSection.tsx
  23. 67
      web/components/home/IntroSection.tsx
  24. 10
      web/components/home/howItWorksContent.ts
  25. 4
      web/components/meta/Meta.tsx
  26. 6
      web/lib/customAxios.ts
  27. 9
      web/next.config.js
  28. 6
      web/pages/_app.tsx
  29. 13
      web/pages/api/hello.ts
  30. 11
      web/pages/dashboard/index.tsx
  31. 26
      web/pages/index.tsx
  32. BIN
      web/public/favicon.ico
  33. BIN
      web/public/images/landing-img1.jpeg
  34. BIN
      web/public/images/smsgatewayandroid.png
  35. 23
      web/services/index.ts
  36. 4
      web/services/types.ts
  37. 2
      web/shared/constants.ts

41
README.md

@ -0,0 +1,41 @@
# TextBee - Android SMS Gateway
A simple SMS gateway that allows users to send SMS messages from a web interface or
from their application via a REST API. It utilizes android phones as SMS gateways.
- **Technology stack**: React, Next.js, Node.js, NestJs, MongoDB, Android, Java
- **Status**: MVP in development, not ready for production use yet
- **Link**: [https://textbee.vernu.dev](https://textbee.vernu.dev/)
![](https://ik.imagekit.io/vernu/textbee/texbee-landing-light.png?updatedAt=1687076964687)
## Usage
1. Go to [textbee.vernu.dev](https://textbee.vernu.dev) and register or login with your account
2. Install the app on your android phone from [textbee.vernu.dev/android](https://textbee.vernu.dev/android)
3. Open the app and grant the permissions for SMS
4. Go to [textbee.vernu.dev/dashboard](https://textbee.vernu.dev/dashboard) and click register device/ generate API Key
5. Scan the QR code with the app or enter the API key manually
6. You are ready to send SMS messages from the dashboard or from your application via the REST API
**Code Snippet**: Few lines of code showing how to send an SMS message via the REST API
```javascript
const API_KEY = 'YOUR_API_KEY';
const DEVICE_ID = 'YOUR_DEVICE_ID';
await axios.post(`https://api.textbee.vernu.dev/api/v1/devices/${DEVICE_ID}/sendSMS?apiKey=${API_KEY}`, {
receivers: [ '+251912345678' ],
smsBody: 'Hello World!',
})
```
## Contributing
Contributions are welcome!
1. Fork the project.
2. Create a feature or bugfix branch from `main` branch.
3. Make sure your commit messages and PR comment summaries are descriptive.
4. Create a pull request to the `main` branch.

2
android/.idea/.name

@ -1 +1 @@
SMS Gateway
TextBee

4
android/app/build.gradle

@ -10,8 +10,8 @@ android {
applicationId "com.vernu.sms" applicationId "com.vernu.sms"
minSdk 24 minSdk 24
targetSdk 32 targetSdk 32
versionCode 5
versionName "1.3.0"
versionCode 7
versionName "2.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }

10
android/app/src/main/java/com/vernu/sms/activities/MainActivity.java

@ -54,7 +54,7 @@ public class MainActivity extends AppCompatActivity {
private static final int SEND_SMS_PERMISSION_REQUEST_CODE = 0; private static final int SEND_SMS_PERMISSION_REQUEST_CODE = 0;
private static final int SCAN_QR_REQUEST_CODE = 49374; private static final int SCAN_QR_REQUEST_CODE = 49374;
private static final String API_BASE_URL = "https://api.sms.vernu.dev/api/v1/";
private static final String API_BASE_URL = "https://api.textbee.vernu.dev/api/v1/";
private String deviceId = null; private String deviceId = null;
@ -89,6 +89,12 @@ public class MainActivity extends AppCompatActivity {
deviceIdTxt.setText(deviceId); deviceIdTxt.setText(deviceId);
deviceBrandAndModelTxt.setText(Build.BRAND + " " + Build.MODEL); deviceBrandAndModelTxt.setText(Build.BRAND + " " + Build.MODEL);
if(deviceId == null || deviceId.isEmpty()) {
registerDeviceBtn.setText("Register");
} else {
registerDeviceBtn.setText("Update");
}
if (isSMSPermissionGranted(mContext)) { if (isSMSPermissionGranted(mContext)) {
grantSMSPermissionBtn.setEnabled(false); grantSMSPermissionBtn.setEnabled(false);
grantSMSPermissionBtn.setText("SMS Permission Granted"); grantSMSPermissionBtn.setText("SMS Permission Granted");
@ -146,7 +152,7 @@ public class MainActivity extends AppCompatActivity {
scanQRBtn.setOnClickListener(view -> { scanQRBtn.setOnClickListener(view -> {
IntentIntegrator intentIntegrator = new IntentIntegrator(MainActivity.this); IntentIntegrator intentIntegrator = new IntentIntegrator(MainActivity.this);
intentIntegrator.setPrompt("Go to sms.vernu.dev/dashboard and click Register Device to generate QR Code");
intentIntegrator.setPrompt("Go to textbee.vernu.dev/dashboard and click Register Device to generate QR Code");
intentIntegrator.setRequestCode(SCAN_QR_REQUEST_CODE); intentIntegrator.setRequestCode(SCAN_QR_REQUEST_CODE);
intentIntegrator.initiateScan(); intentIntegrator.initiateScan();
}); });

2
android/app/src/main/res/layout/activity_main.xml

@ -27,7 +27,7 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Go to sms.vernu.dev/dashboard and click register device, then copy and paste the api key generated or scan the QR code" />
android:text="Go to textbee.vernu.dev/dashboard and click register device, then copy and paste the api key generated or scan the QR code" />
<Button <Button
android:id="@+id/grantSMSPermissionBtn" android:id="@+id/grantSMSPermissionBtn"

8
android/app/src/main/res/values-night/themes.xml

@ -2,12 +2,12 @@
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="Theme.SMSGateway" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> <style name="Theme.SMSGateway" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. --> <!-- Primary brand color. -->
<item name="colorPrimary">#f35b04</item>
<item name="colorPrimaryVariant">#f18701</item>
<item name="colorPrimary">#4299E1</item>
<item name="colorPrimaryVariant">#4299cc</item>
<item name="colorOnPrimary">@color/white</item> <item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. --> <!-- Secondary brand color. -->
<item name="colorSecondary">#3d348b</item>
<item name="colorSecondaryVariant">#7678ed</item>
<item name="colorSecondary">#f35b04</item>
<item name="colorSecondaryVariant">#f18701</item>
<item name="colorOnSecondary">@color/black</item> <item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. --> <!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item> <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>

2
android/app/src/main/res/values/strings.xml

@ -1,3 +1,3 @@
<resources> <resources>
<string name="app_name">SMS Gateway</string>
<string name="app_name">TextBee</string>
</resources> </resources>

8
android/app/src/main/res/values/themes.xml

@ -2,12 +2,12 @@
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="Theme.SMSGateway" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> <style name="Theme.SMSGateway" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. --> <!-- Primary brand color. -->
<item name="colorPrimary">#f35b04</item>
<item name="colorPrimaryVariant">#f18701</item>
<item name="colorPrimary">#4299E1</item>
<item name="colorPrimaryVariant">#4299cc</item>
<item name="colorOnPrimary">@color/white</item> <item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. --> <!-- Secondary brand color. -->
<item name="colorSecondary">#3d348b</item>
<item name="colorSecondaryVariant">#7678ed</item>
<item name="colorSecondary">#f35b04</item>
<item name="colorSecondaryVariant">#f18701</item>
<item name="colorOnSecondary">@color/black</item> <item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. --> <!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item> <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>

2
android/settings.gradle

@ -12,5 +12,5 @@ dependencyResolutionManagement {
mavenCentral() mavenCentral()
} }
} }
rootProject.name = "SMS Gateway"
rootProject.name = "TextBee"
include ':app' include ':app'

4
api/src/main.ts

@ -16,8 +16,8 @@ async function bootstrap() {
}) })
const config = new DocumentBuilder() const config = new DocumentBuilder()
.setTitle('VERNU SMS Gateway api docs')
.setDescription('api docs')
.setTitle('TextBee API Docs')
.setDescription('TextBee - Android SMS Gateway API Docs')
.setVersion('1.0') .setVersion('1.0')
.addBearerAuth() .addBearerAuth()
.build() .build()

2
web/.env.example

@ -1,2 +1,2 @@
NEXT_PUBLIC_API_BASE_URL=https://api.sms.vernu.dev/api/v1
NEXT_PUBLIC_API_BASE_URL=https://api.textbee.vernu.dev/api/v1
NEXT_PUBLIC_GOOGLE_CLIENT_ID= NEXT_PUBLIC_GOOGLE_CLIENT_ID=

52
web/components/Footer.tsx

@ -0,0 +1,52 @@
import {
Box,
chakra,
Container,
Stack,
Text,
useColorModeValue,
} from '@chakra-ui/react'
import Link from 'next/link'
export default function Footer() {
return (
<Box
bg={useColorModeValue('gray.50', 'gray.900')}
color={useColorModeValue('gray.700', 'gray.200')}
>
<Container
as={Stack}
maxW={'6xl'}
py={4}
spacing={4}
justify={'center'}
align={'center'}
>
<Stack direction={'row'} spacing={6}>
<Link href='/'>Home</Link>
<Link href='/dashboard'>Dashboard</Link>
<Link href='/android'>Download App</Link>
<Link href='https://github.com/vernu/textbee'>Github</Link>
</Stack>
</Container>
<Box
borderTopWidth={1}
borderStyle={'solid'}
borderColor={useColorModeValue('gray.200', 'gray.700')}
>
<Container
as={Stack}
maxW={'6xl'}
py={4}
direction={{ base: 'column', md: 'row' }}
spacing={4}
justify='center'
align={{ base: 'center', md: 'center' }}
>
<Text>© {new Date().getFullYear()} All rights reserved</Text>
</Container>
</Box>
</Box>
)
}

17
web/components/Navbar.tsx

@ -27,7 +27,12 @@ export default function Navbar() {
return ( return (
<> <>
<Box bg={useColorModeValue('gray.100', 'gray.700')} px={4} shadow='lg' mb={1}>
<Box
bg={useColorModeValue('gray.100', 'blue.600')}
px={4}
shadow='lg'
mb={1}
>
<Flex h={16} alignItems={'center'} justifyContent={'space-between'}> <Flex h={16} alignItems={'center'} justifyContent={'space-between'}>
<Link href='/' passHref> <Link href='/' passHref>
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
@ -37,9 +42,10 @@ export default function Navbar() {
w={'30px'} w={'30px'}
h={'30px'} h={'30px'}
src={'/images/sms-gateway-logo.png'} src={'/images/sms-gateway-logo.png'}
borderRadius='full'
/> />
<Box style={{ cursor: 'pointer', marginLeft: '5px' }}> <Box style={{ cursor: 'pointer', marginLeft: '5px' }}>
VERNU SMS
TextBee
</Box> </Box>
</Flex> </Flex>
</Link> </Link>
@ -50,6 +56,12 @@ export default function Navbar() {
{colorMode === 'light' ? <MoonIcon /> : <SunIcon />} {colorMode === 'light' ? <MoonIcon /> : <SunIcon />}
</Button> </Button>
<Menu>
<Link href='https://github.com/vernu/textbee' passHref>
<MenuButton>Github</MenuButton>
</Link>
</Menu>
{!user ? ( {!user ? (
<> <>
<Menu> <Menu>
@ -102,7 +114,6 @@ export default function Navbar() {
> >
Dashboard Dashboard
</MenuItem> </MenuItem>
<MenuItem>Account Settings</MenuItem>
<MenuItem <MenuItem
onClick={() => { onClick={() => {
dispatch(logout()) dispatch(logout())

11
web/components/dashboard/DeviceList.tsx

@ -64,20 +64,13 @@ const DeviceList = () => {
<Tr key={_id}> <Tr key={_id}>
<Td>{`${brand}/ ${model}`}</Td> <Td>{`${brand}/ ${model}`}</Td>
<Td>{enabled ? 'enabled' : 'disabled'}</Td> <Td>{enabled ? 'enabled' : 'disabled'}</Td>
<Td>
<EmailIcon onDoubleClick={(e) => {}} />
</Td>
<Td>{/* <EmailIcon onDoubleClick={(e) => {}} /> */}</Td>
<Td> <Td>
<Tooltip label='Double Click to delete'> <Tooltip label='Double Click to delete'>
<IconButton <IconButton
aria-label='Delete' aria-label='Delete'
icon={<DeleteIcon />} icon={<DeleteIcon />}
onDoubleClick={(e) => {
sendSMSRequest(_id, {
receivers: ['+251912657519'],
smsBody: 'Hello World',
})
}}
onDoubleClick={(e) => {}}
/> />
</Tooltip> </Tooltip>
</Td> </Td>

19
web/components/dashboard/GenerateApiKey.tsx

@ -1,4 +1,5 @@
import { import {
Box,
Button, Button,
chakra, chakra,
Flex, Flex,
@ -112,8 +113,17 @@ export default function GenerateApiKey() {
} }
return ( return (
<> <>
<Box padding={5} border='1px solid gray' marginBottom={10} borderRadius='2xl'>
<Flex direction='row' justifyContent='space-between'>
{' '} {' '}
<Flex justifyContent='center'>
<chakra.h1
fontSize='md'
fontWeight='bold'
mt={2}
color={useColorModeValue('gray.800', 'white')}
>
Generate Api Key and Register Device
</chakra.h1>
<Button <Button
/* flex={1} */ /* flex={1} */
px={4} px={4}
@ -133,11 +143,10 @@ export default function GenerateApiKey() {
onClick={generateApiKey} onClick={generateApiKey}
disabled={generatingApiKey} disabled={generatingApiKey}
> >
{generatingApiKey
? 'generating... '
: 'Generate Api Key/ Register Device'}
{generatingApiKey ? 'loading... ' : 'Get Started'}
</Button> </Button>
</Flex>
</Flex>{' '}
</Box>
{generatedApiKey && ( {generatedApiKey && (
<> <>
{ {

7
web/components/dashboard/SendSMS.tsx

@ -1,6 +1,7 @@
import { import {
Box, Box,
Button, Button,
Flex,
FormLabel, FormLabel,
Input, Input,
Modal, Modal,
@ -53,7 +54,11 @@ export default function SendSMS() {
return ( return (
<> <>
<Button onClick={onOpen}>Start Sending</Button>
<Flex justifyContent='flex-end' marginBottom={20}>
<Button bg={'blue.400'} color={'white'} onClick={onOpen}>
Send SMS
</Button>
</Flex>
<Modal isOpen={isOpen} onClose={onClose}> <Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay /> <ModalOverlay />

16
web/components/dashboard/UserStats.tsx

@ -1,14 +1,21 @@
import { Box, SimpleGrid, chakra } from '@chakra-ui/react' import { Box, SimpleGrid, chakra } from '@chakra-ui/react'
import React from 'react' import React from 'react'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { selectApiKeyList } from '../../store/apiKeyListReducer'
import { selectAuth } from '../../store/authReducer' import { selectAuth } from '../../store/authReducer'
import { selectDeviceList } from '../../store/deviceListReducer'
import UserStatsCard from './UserStatsCard' import UserStatsCard from './UserStatsCard'
const UserStats = () => { const UserStats = () => {
const { user: currentUser } = useSelector(selectAuth) const { user: currentUser } = useSelector(selectAuth)
const { data: deviceListData } = useSelector(selectDeviceList)
const { data: apiKeyListData } = useSelector(selectApiKeyList)
return ( return (
<> <>
<Box maxW='7xl' mx={'auto'} pt={5} px={{ base: 2, sm: 12, md: 17 }}> <Box maxW='7xl' mx={'auto'} pt={5} px={{ base: 2, sm: 12, md: 17 }}>
<SimpleGrid columns={{ base: 1, md: 2 }} >
<chakra.h1 <chakra.h1
textAlign={'center'} textAlign={'center'}
fontSize={'4xl'} fontSize={'4xl'}
@ -17,10 +24,11 @@ const UserStats = () => {
> >
Welcome {currentUser?.name} Welcome {currentUser?.name}
</chakra.h1> </chakra.h1>
<SimpleGrid columns={{ base: 1, md: 3 }} spacing={{ base: 5, lg: 8 }}>
<UserStatsCard title={'Registered '} stat={'2 devices'} />
<UserStatsCard title={'Generated'} stat={'3 API Keys'} />
<UserStatsCard title={'Sent'} stat={'100 SMS'} />
<SimpleGrid columns={{ base: 3 }} spacing={{ base: 5, lg: 8 }}>
<UserStatsCard title={'Registered '} stat={`${deviceListData?.length || '-:-'} Devices`} />
<UserStatsCard title={'Generated'} stat={`${apiKeyListData?.length || '-:-'} API Keys`} />
<UserStatsCard title={'Sent'} stat={'-:- SMS'} />
</SimpleGrid>
</SimpleGrid> </SimpleGrid>
</Box> </Box>
</> </>

5
web/components/dashboard/UserStatsCard.tsx

@ -16,11 +16,14 @@ export default function UserStatsCard({ ...props }) {
border={'1px solid'} border={'1px solid'}
borderColor={useColorModeValue('gray.800', 'gray.500')} borderColor={useColorModeValue('gray.800', 'gray.500')}
rounded={'lg'} rounded={'lg'}
style={{
height: '90px'
}}
> >
<StatLabel fontWeight={'medium'} isTruncated> <StatLabel fontWeight={'medium'} isTruncated>
{title} {title}
</StatLabel> </StatLabel>
<StatNumber fontSize={'2xl'} fontWeight={'medium'}>
<StatNumber fontSize={'md'} fontWeight={'bold'}>
{stat} {stat}
</StatNumber> </StatNumber>
</Stat> </Stat>

42
web/components/home/CodeSnippetSection.tsx

@ -0,0 +1,42 @@
import { Box, Flex, Heading, Image, Text } from '@chakra-ui/react'
import React from 'react'
export default function CodeSnippetSection() {
return (
<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>
)
}

88
web/components/home/DownloadAppSection.tsx

@ -0,0 +1,88 @@
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>
)
}

39
web/components/home/FeaturesSection.tsx

@ -8,37 +8,46 @@ import {
SimpleGrid, SimpleGrid,
Stack, Stack,
Text, Text,
useColorModeValue,
VStack, VStack,
} from '@chakra-ui/react' } from '@chakra-ui/react'
import React from 'react' import React from 'react'
import { featuresContent } from './featuresContent' import { featuresContent } from './featuresContent'
const FeaturesSection = () => {
export default function FeaturesSection() {
const boxBgColor = useColorModeValue('gray.100', 'gray.800')
return ( return (
<Box p={4}>
<Stack spacing={4} as={Container} maxW={'6xl'}>
<Heading fontSize={'3xl'} textAlign={'center'} pb={8}>
<Box p={4} my={16} maxW={'6xl'}>
<Heading fontSize={'3xl'} textAlign={'center'} pb={0}>
Features Features
</Heading> </Heading>
<Text color={'gray.600'} fontSize={'lg'} textAlign={'justify'}>
<Text color={'gray.600'} fontSize={'lg'} textAlign={'center'}>
The ultimate solution for your messaging needs! Our free open-source 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.
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> </Text>
</Stack>
<Container maxW={'6xl'} mt={10}>
<Container maxW={'6xl'} mt={0}>
<SimpleGrid columns={{ base: 1, md: 2, lg: 4 }} spacing={3} pt={16}> <SimpleGrid columns={{ base: 1, md: 2, lg: 4 }} spacing={3} pt={16}>
{featuresContent.map((feature, i) => ( {featuresContent.map((feature, i) => (
<HStack key={i} align={'top'} borderWidth="1px" borderRadius="md" p={2} shadow='lg' >
<HStack
key={i}
align={'top'}
borderWidth='1px'
borderRadius='sm'
p={2}
shadow='lg'
background={boxBgColor}
>
<Box color={'green.400'} px={1}> <Box color={'green.400'} px={1}>
<Icon as={CheckIcon} /> <Icon as={CheckIcon} />
</Box> </Box>
<VStack align={'start'}> <VStack align={'start'}>
<Text fontWeight={600}>{feature.title}</Text>
<Text color={'gray.600'}>{feature.description}</Text>
<Text fontWeight={800}>{feature.title}</Text>
<Text fontWeight='normal'>{feature.description}</Text>
</VStack> </VStack>
</HStack> </HStack>
))} ))}
@ -47,5 +56,3 @@ const FeaturesSection = () => {
</Box> </Box>
) )
} }
export default FeaturesSection

22
web/components/home/HowItWorksSection.tsx

@ -2,13 +2,11 @@ import { AddIcon, MinusIcon } from '@chakra-ui/icons'
import { import {
Accordion, Accordion,
AccordionButton, AccordionButton,
AccordionIcon,
AccordionItem, AccordionItem,
AccordionPanel, AccordionPanel,
Box, Box,
Container, Container,
Heading, Heading,
Stack,
Text, Text,
} from '@chakra-ui/react' } from '@chakra-ui/react'
import React from 'react' import React from 'react'
@ -16,22 +14,20 @@ import { howItWorksContent } from './howItWorksContent'
export default function HowItWorksSection() { export default function HowItWorksSection() {
return ( return (
<Box p={4}>
<Stack spacing={4} as={Container} maxW={'6xl'}>
<a id='#how-it-works'>
<Heading fontSize={'3xl'} textAlign={'center'} py={8}>
<Box px={4} my={24} maxW={'6xl'}>
{/* @ts-ignore */}
<a name='how-it-works'>
<Heading fontSize={'3xl'} textAlign={'center'}>
How It Works How It Works
</Heading> </Heading>
</a> </a>
<Text color={'gray.600'} fontSize={'lg'} textAlign={'justify'}>
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Illo
exercitationem quo quibusdam, fugit quaerat odio quisquam commodi ut?
Aliquid ab sapiente, expedita quas neque amet consectetur quisquam
reprehenderit voluptas commodi?
<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> </Text>
</Stack>
<Container maxW={'6xl'} mt={10} pt={8}>
<Container maxW={'6xl'} mt={10} pt={0}>
<Accordion allowMultiple defaultIndex={[]}> <Accordion allowMultiple defaultIndex={[]}>
{howItWorksContent.map(({ title, description }) => ( {howItWorksContent.map(({ title, description }) => (
<AccordionItem key={title}> <AccordionItem key={title}>

67
web/components/home/IntroSection.tsx

@ -7,15 +7,27 @@ import {
Text, Text,
Button, Button,
Image, Image,
IconButton,
createIcon, createIcon,
} from '@chakra-ui/react' } from '@chakra-ui/react'
import Link from 'next/link' import Link from 'next/link'
import Router from 'next/router' 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() { export default function IntroSection() {
const { currentUser } = useSelector(selectAuth)
const handleGetStarted = () => {
if (!currentUser) {
Router.push('/register')
} else {
Router.push('/dashboard')
}
}
return ( return (
<Container maxW={'7xl'}>
<Container maxW={'7xl'} py={8}>
<Stack <Stack
align={'center'} align={'center'}
spacing={{ base: 8, md: 10 }} spacing={{ base: 8, md: 10 }}
@ -28,28 +40,21 @@ export default function IntroSection() {
fontWeight={600} fontWeight={600}
fontSize={{ base: '3xl', sm: '4xl', lg: '5xl' }} fontSize={{ base: '3xl', sm: '4xl', lg: '5xl' }}
> >
<Text
as={'span'}
position={'relative'}
_after={{
content: "''",
width: 'full',
height: '30%',
position: 'absolute',
bottom: 1,
left: 0,
bg: 'blue.400',
zIndex: -1,
}}
>
VERNU SMS Gateway,
<Text as={'span'} position={'relative'} fontWeight={600}>
<ChatIcon /> Text
<Text as={'span'} color={'blue.400'} decoration='underline'>
Bee
</Text>
</Text> </Text>
<br /> <br />
<Text as={'span'} color={'blue.400'}>
<Text as={'span'} color={'blue.400'} fontWeight={300}>
Make your android device a portable SMS Gateway! Make your android device a portable SMS Gateway!
</Text> </Text>
</Heading> </Heading>
<Text color={'gray.500'}>
<Text
color={'gray.500'}
fontSize={{ base: 'md', sm: 'lg', lg: 'xl' }}
>
Unlock the power of messaging with our open-source Android SMS Unlock the power of messaging with our open-source Android SMS
Gateway. Gateway.
</Text> </Text>
@ -65,9 +70,7 @@ export default function IntroSection() {
colorScheme={'blue'} colorScheme={'blue'}
bg={'blue.400'} bg={'blue.400'}
_hover={{ bg: 'blue.500' }} _hover={{ bg: 'blue.500' }}
onClick={() => {
Router.push('/register')
}}
onClick={handleGetStarted}
> >
Get Started Get Started
</Button> </Button>
@ -95,29 +98,17 @@ export default function IntroSection() {
position={'relative'} position={'relative'}
height={'400px'} height={'400px'}
rounded={'2xl'} rounded={'2xl'}
boxShadow={'2xl'}
boxShadow={'xs'}
width={'full'} width={'full'}
overflow={'hidden'} overflow={'hidden'}
> >
<IconButton
aria-label={'Play Button'}
variant={'ghost'}
_hover={{ bg: 'transparent' }}
icon={<PlayIcon w={12} h={12} />}
size={'lg'}
color={'white'}
position={'absolute'}
left={'50%'}
top={'50%'}
transform={'translateX(-50%) translateY(-50%)'}
/>
<Image <Image
alt={'Hero Image'} alt={'Hero Image'}
fit={'cover'} fit={'cover'}
align={'center'} align={'center'}
w={'100%'}
h={'100%'}
src={'/images/landing-img1.jpeg'}
// w={'100%'}
// h={'100%'}
src={'/images/smsgatewayandroid.png'}
/> />
</Box> </Box>
</Flex> </Flex>

10
web/components/home/howItWorksContent.ts

@ -1,23 +1,23 @@
export const howItWorksContent = [ export const howItWorksContent = [
{ {
title: 'Step 1: Download The Android App',
title: 'Step 1: Download The Android App from textbee.vernu.dev/android',
description: description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
'',
}, },
{ {
title: 'Step 2: Generate an API Key from the dashboard', title: 'Step 2: Generate an API Key from the dashboard',
description: description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
'',
}, },
{ {
title: title:
'Step 3: Scan the QR/ enter your api key manually and enable the gateway app', 'Step 3: Scan the QR/ enter your api key manually and enable the gateway app',
description: description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
'',
}, },
{ {
title: 'Step 4: Start sending', title: 'Step 4: Start sending',
description: description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
'You can now send SMS from the dashboard. or visit the API docs at https://api.textbee.vernu.dev to send SMS programatically',
}, },
] ]

4
web/components/meta/Meta.tsx

@ -3,10 +3,10 @@ import Head from 'next/head'
export default function Meta() { export default function Meta() {
return ( return (
<Head> <Head>
<title>SMS Gateway</title>
<title>TextBee - SMS Gateway</title>
<meta name='viewport' content='initial-scale=1.0, width=device-width' /> <meta name='viewport' content='initial-scale=1.0, width=device-width' />
<meta name='description' content='Android SMS Gateway' /> <meta name='description' content='Android SMS Gateway' />
<meta name='keywords' content='android, sms, gateway, sms-gateway' />
<meta name='keywords' content='android, text, sms, gateway, sms-gateway' />
<meta name='author' content='Israel Abebe' /> <meta name='author' content='Israel Abebe' />
<link rel='icon' href='/favicon.ico' /> <link rel='icon' href='/favicon.ico' />
</Head> </Head>

6
web/lib/axiosInstance.ts → web/lib/customAxios.ts

@ -1,11 +1,11 @@
import axios from 'axios' import axios from 'axios'
import { LOCAL_STORAGE_KEY } from '../shared/constants' import { LOCAL_STORAGE_KEY } from '../shared/constants'
const axiosInstance = axios.create({
const customAxios = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_BASE_URL, baseURL: process.env.NEXT_PUBLIC_API_BASE_URL,
}) })
axiosInstance.interceptors.request.use((config) => {
customAxios.interceptors.request.use((config) => {
const token = localStorage.getItem(LOCAL_STORAGE_KEY.TOKEN) const token = localStorage.getItem(LOCAL_STORAGE_KEY.TOKEN)
if (token) { if (token) {
config.headers.Authorization = `Bearer ${token}` config.headers.Authorization = `Bearer ${token}`
@ -13,4 +13,4 @@ axiosInstance.interceptors.request.use((config) => {
return config return config
}) })
export default axiosInstance
export default customAxios

9
web/next.config.js

@ -1,6 +1,15 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
reactStrictMode: true, reactStrictMode: true,
async redirects() {
return [
{
source: '/android',
destination: 'https://appdistribution.firebase.dev/i/1439f7af2d1e8e8e',
permanent: false,
},
]
},
} }
module.exports = nextConfig module.exports = nextConfig

6
web/pages/_app.tsx

@ -2,11 +2,12 @@ import '../styles/globals.css'
import type { AppProps } from 'next/app' import type { AppProps } from 'next/app'
import { Provider } from 'react-redux' import { Provider } from 'react-redux'
import { store } from '../store/store' import { store } from '../store/store'
import { ChakraProvider } from '@chakra-ui/react'
import { Box, ChakraProvider } from '@chakra-ui/react'
import Navbar from '../components/Navbar' import Navbar from '../components/Navbar'
import Meta from '../components/meta/Meta' import Meta from '../components/meta/Meta'
import { GoogleOAuthProvider } from '@react-oauth/google' import { GoogleOAuthProvider } from '@react-oauth/google'
import ErrorBoundary from '../components/ErrorBoundary' import ErrorBoundary from '../components/ErrorBoundary'
import Footer from '../components/Footer'
function MyApp({ Component, pageProps }: AppProps) { function MyApp({ Component, pageProps }: AppProps) {
return ( return (
<ErrorBoundary> <ErrorBoundary>
@ -20,6 +21,7 @@ function MyApp({ Component, pageProps }: AppProps) {
<Wrapper> <Wrapper>
<Component {...pageProps} /> <Component {...pageProps} />
</Wrapper> </Wrapper>
<Footer />
</ChakraProvider> </ChakraProvider>
</GoogleOAuthProvider> </GoogleOAuthProvider>
</Provider> </Provider>
@ -28,7 +30,7 @@ function MyApp({ Component, pageProps }: AppProps) {
} }
const Wrapper = ({ children }) => { const Wrapper = ({ children }) => {
return <>{children}</>
return <Box minH='75vh'>{children}</Box>
} }
export default MyApp export default MyApp

13
web/pages/api/hello.ts

@ -1,13 +0,0 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next'
type Data = {
name: string
}
export default function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
res.status(200).json({ name: 'John Doe' })
}

11
web/pages/dashboard/index.tsx

@ -28,19 +28,18 @@ export default function Dashboard() {
<> <>
<UserStats /> <UserStats />
<Box maxW='7xl' mx={'auto'} pt={5} px={{ base: 2, sm: 12, md: 17 }}> <Box maxW='7xl' mx={'auto'} pt={5} px={{ base: 2, sm: 12, md: 17 }}>
<Flex justifyContent='space-evenly'>
<GenerateApiKey />
<SendSMS />
</Flex>
<Flex justifyContent='space-eve nly'></Flex>
<br /> <br />
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={{ base: 5, lg: 8 }}> <SimpleGrid columns={{ base: 1, md: 2 }} spacing={{ base: 5, lg: 8 }}>
<Box backdropBlur='2xl' borderWidth='1px' borderRadius='lg'>
<Box backdropBlur='2xl' borderWidth='0px' borderRadius='lg'>
<GenerateApiKey />
<ErrorBoundary> <ErrorBoundary>
<ApiKeyList /> <ApiKeyList />
</ErrorBoundary> </ErrorBoundary>
</Box> </Box>
<Box backdropBlur='2xl' borderWidth='1px' borderRadius='lg'>
<Box backdropBlur='2xl' borderWidth='0px' borderRadius='lg'>
<SendSMS />
<ErrorBoundary> <ErrorBoundary>
<DeviceList /> <DeviceList />
</ErrorBoundary> </ErrorBoundary>

26
web/pages/index.tsx

@ -1,28 +1,17 @@
import { Box, Container } from '@chakra-ui/react'
import { Container } from '@chakra-ui/react'
import { useGoogleOneTapLogin } from '@react-oauth/google' import { useGoogleOneTapLogin } from '@react-oauth/google'
import Image from 'next/image'
import Router from 'next/router'
import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import FeaturesSection from '../components/home/FeaturesSection' import FeaturesSection from '../components/home/FeaturesSection'
import HowItWorksSection from '../components/home/HowItWorksSection' import HowItWorksSection from '../components/home/HowItWorksSection'
import IntroSection from '../components/home/IntroSection' import IntroSection from '../components/home/IntroSection'
import { loginWithGoogle, selectAuth } from '../store/authReducer' import { loginWithGoogle, selectAuth } from '../store/authReducer'
import wageSvg from '../public/images/wave.svg'
const Wave = ({ rotate }: { rotate?: boolean }) => (
<Box transform={rotate ? 'rotate(180deg)' : ''}>
<Image src={wageSvg} alt={'wave'} />
</Box>
)
import DownloadAppSection from '../components/home/DownloadAppSection'
import CodeSnippetSection from '../components/home/CodeSnippetSection'
export default function HomePage() { export default function HomePage() {
const { accessToken, user } = useSelector(selectAuth)
useEffect(() => {
if (accessToken && user) {
Router.push('/dashboard')
}
}, [accessToken, user])
const { user } = useSelector(selectAuth)
const dispatch = useDispatch() const dispatch = useDispatch()
@ -41,11 +30,10 @@ export default function HomePage() {
return ( return (
<Container maxW={'7xl'}> <Container maxW={'7xl'}>
<IntroSection /> <IntroSection />
<Wave rotate />
<FeaturesSection /> <FeaturesSection />
<Wave />
<HowItWorksSection /> <HowItWorksSection />
<Wave />
<DownloadAppSection />
<CodeSnippetSection />
</Container> </Container>
) )
} }

BIN
web/public/favicon.ico

BIN
web/public/images/landing-img1.jpeg

Before

Width: 416  |  Height: 416  |  Size: 24 KiB

BIN
web/public/images/smsgatewayandroid.png

After

Width: 1080  |  Height: 1545  |  Size: 242 KiB

23
web/services/index.ts

@ -1,4 +1,4 @@
import axiosInstance from '../lib/axiosInstance'
import axios from '../lib/customAxios'
import { import {
GoogleLoginRequestPayload, GoogleLoginRequestPayload,
LoginRequestPayload, LoginRequestPayload,
@ -11,41 +11,46 @@ import {
export const loginRequest = async ( export const loginRequest = async (
payload: LoginRequestPayload payload: LoginRequestPayload
): Promise<LoginResponse> => { ): Promise<LoginResponse> => {
const res = await axiosInstance.post(`/auth/login`, payload)
const res = await axios.post(`/auth/login`, payload)
return res.data.data return res.data.data
} }
export const loginWithGoogleRequest = async ( export const loginWithGoogleRequest = async (
payload: GoogleLoginRequestPayload payload: GoogleLoginRequestPayload
): Promise<LoginResponse> => { ): Promise<LoginResponse> => {
const res = await axiosInstance.post(`/auth/google-login`, payload)
const res = await axios.post(`/auth/google-login`, payload)
return res.data.data return res.data.data
} }
export const registerRequest = async ( export const registerRequest = async (
payload: RegisterRequestPayload payload: RegisterRequestPayload
): Promise<RegisterResponse> => { ): Promise<RegisterResponse> => {
const res = await axiosInstance.post(`/auth/register`, payload)
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 return res.data.data
} }
export const generateApiKeyRequest = async () => { export const generateApiKeyRequest = async () => {
const res = await axiosInstance.post(`/auth/api-keys`, {})
const res = await axios.post(`/auth/api-keys`, {})
return res.data.data return res.data.data
} }
export const getApiKeyListRequest = async () => { export const getApiKeyListRequest = async () => {
const res = await axiosInstance.get(`/auth/api-keys`)
const res = await axios.get(`/auth/api-keys`)
return res.data.data return res.data.data
} }
export const deleteApiKeyRequest = async (id: string) => { export const deleteApiKeyRequest = async (id: string) => {
const res = await axiosInstance.delete(`/auth/api-keys/${id}`)
const res = await axios.delete(`/auth/api-keys/${id}`)
return res.data.data return res.data.data
} }
export const getDeviceListRequest = async () => { export const getDeviceListRequest = async () => {
const res = await axiosInstance.get(`/gateway/devices`)
const res = await axios.get(`/gateway/devices`)
return res.data.data return res.data.data
} }
@ -53,7 +58,7 @@ export const sendSMSRequest = async (
deviceId: string, deviceId: string,
payload: SendSMSRequestPayload payload: SendSMSRequestPayload
) => { ) => {
const res = await axiosInstance.post(
const res = await axios.post(
`/gateway/devices/${deviceId}/sendSMS`, `/gateway/devices/${deviceId}/sendSMS`,
payload payload
) )

4
web/services/types.ts

@ -43,6 +43,10 @@ export interface LoginResponse extends BaseResponse {
export type RegisterResponse = LoginResponse export type RegisterResponse = LoginResponse
export interface CurrentUserResponse extends BaseResponse {
data: UserEntity
}
export interface SendSMSRequestPayload { export interface SendSMSRequestPayload {
receivers: string[] receivers: string[]
smsBody: string smsBody: string

2
web/shared/constants.ts

@ -1,4 +1,4 @@
export const KEY_PREFIX = 'sms0'
export const KEY_PREFIX = 'textbee'
export const LOCAL_STORAGE_KEY = { export const LOCAL_STORAGE_KEY = {
USER: `${KEY_PREFIX}.user`, USER: `${KEY_PREFIX}.user`,

Loading…
Cancel
Save