├── .github └── pull-request-template.md ├── .gitignore ├── App.tsx ├── README.md ├── Server ├── controllers │ └── userController.js ├── models │ └── models.ts └── server.js ├── app.json ├── assets └── images │ ├── favicon.png │ ├── icon.png │ └── splash.png ├── babel.config.js ├── components ├── JobListing.tsx └── __tests__ │ └── component_test.js ├── hooks ├── useCachedResources.ts └── useJobQuery.ts ├── package-lock.json ├── package.json ├── screens ├── JobPage.tsx ├── Login.tsx ├── NotFoundScreen.tsx ├── SearchContainer.tsx └── SignUp.tsx ├── tsconfig.json └── types.tsx /.github/pull-request-template.md: -------------------------------------------------------------------------------- 1 | ## Types of changes 2 | 3 | - [ ] Bugfix (change which fixes an issue) 4 | - [ ] New feature (change which adds functionality) 5 | - [ ] Refactor (change which changes the codebase without affecting its external behavior) 6 | - [ ] Non-breaking change (fix or feature that would causes existing functionality to work as expected) 7 | - [ ] Breaking change (fix or feature that would cause existing functionality to __not__ work as expected) 8 | ## Purpose 9 | 10 | ## Approach 11 | 12 | ## Resources 13 | 14 | ## Screenshot(s) 15 | 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | *.jks 5 | *.p8 6 | *.p12 7 | *.key 8 | *.mobileprovision 9 | *.orig.* 10 | web-build/ 11 | .env 12 | package-lock.json 13 | 14 | # macOS 15 | .DS_Store 16 | .env 17 | -------------------------------------------------------------------------------- /App.tsx: -------------------------------------------------------------------------------- 1 | import { StatusBar } from "expo-status-bar"; 2 | import React from "react"; 3 | import { SafeAreaProvider } from "react-native-safe-area-context"; 4 | import { Router, Switch, Route } from "react-router-native"; 5 | import { Right, Icon, Left, Header, Container, Body, Title } from "native-base"; 6 | 7 | import useCachedResources from "./hooks/useCachedResources"; 8 | import Login from "./screens/Login"; 9 | import SignUp from "./screens/SignUp"; 10 | import JobPage from "./screens/JobPage"; 11 | import SearchContainer from "./screens/SearchContainer"; 12 | 13 | const createHistory = require('history').createMemoryHistory; 14 | const history = createHistory(); 15 | 16 | export default function App() { 17 | const isLoadingComplete = useCachedResources(); 18 | if (!isLoadingComplete) { 19 | return null; 20 | } else { 21 | return ( 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | history.goBack()} name="arrow-back" /> 31 | 32 | 33 | TechLander 34 | 35 | 36 |
37 | 38 | 39 | 40 | 41 |
42 |
43 |
44 |
45 |
46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TechLander 2 | -------------------------------------------------------------------------------- /Server/controllers/userController.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require("bcrypt"); 2 | const saltRounds = 10; 3 | const db = require("../models/models.ts"); 4 | const userController = {}; 5 | 6 | userController.login = (req, res, next) => { 7 | if (req.body.username && req.body.password) { 8 | const query = `SELECT password, jobs FROM users WHERE username = '${req.body.username}';`; 9 | db.query(query) 10 | .then((data) => { 11 | bcrypt 12 | .compare(req.body.password, data.rows[0].password) 13 | .then((match) => { 14 | res.locals.check = match; 15 | res.locals.jobs = data.rows[0].jobs; 16 | next(); 17 | }) 18 | .catch((err) => next(err)); 19 | }) 20 | .catch((err) => console.log(err.message)); 21 | } 22 | }; 23 | 24 | userController.signUp = (req, res, next) => { 25 | if (req.body.username && req.body.password) { 26 | bcrypt 27 | .hash(req.body.password, saltRounds) 28 | .then((hash) => { 29 | console.log(`Hash: ${hash}`); 30 | const query = `INSERT INTO users (username, password, jobs) 31 | VALUES ('${req.body.username}','${hash}', '[]');`; 32 | db.query(query) 33 | .then(() => { 34 | res.locals.check = true; 35 | next(); 36 | }) 37 | .catch((err) => { 38 | next(err); 39 | }); 40 | }) 41 | .catch((err) => console.log(err.message)); 42 | } 43 | }; 44 | 45 | userController.addJobs = (req, res, next) => { 46 | // req.body.jobs array of jobs 47 | const query = ``; 48 | db.query(query).then((data) => console.log(data)); 49 | }; 50 | 51 | module.exports = userController; 52 | -------------------------------------------------------------------------------- /Server/models/models.ts: -------------------------------------------------------------------------------- 1 | const { Pool } = require("pg"); 2 | require("dotenv").config(); 3 | 4 | const pool = new Pool({ 5 | connectionString: process.env.PG_URI, 6 | }); 7 | 8 | module.exports = { 9 | query: (text, params, callback) => { 10 | console.log("executed query", text); 11 | return pool.query(text, params, callback); 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /Server/server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const app = express(); 3 | const userController = require("./controllers/userController"); 4 | 5 | app.use(express.json()); 6 | 7 | app.post("/login", userController.login, (req, res) => { 8 | const user = { check: res.locals.check, jobs: res.locals.jobs }; 9 | res.status(200).send(user); 10 | }); 11 | 12 | app.post("/signUp", userController.signUp, (req, res) => { 13 | res.status(200).send(res.locals.check); 14 | }); 15 | 16 | app.use("*", (req, res) => { 17 | res.sendStatus(404); 18 | }); 19 | 20 | app.use((err, req, res, next) => { 21 | console.log("err", err); 22 | }); 23 | 24 | app.listen(19000, () => console.log("i am listening")); 25 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "TechLander", 4 | "slug": "TechLander", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/images/icon.png", 8 | "scheme": "myapp", 9 | "userInterfaceStyle": "automatic", 10 | "splash": { 11 | "image": "./assets/images/splash.png", 12 | "resizeMode": "contain", 13 | "backgroundColor": "#ffffff" 14 | }, 15 | "updates": { 16 | "fallbackToCacheTimeout": 0 17 | }, 18 | "assetBundlePatterns": [ 19 | "**/*" 20 | ], 21 | "ios": { 22 | "supportsTablet": true 23 | }, 24 | "web": { 25 | "favicon": "./assets/images/favicon.png" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TechLanderThereCanOnlyBeOne/TechLander/fb5931a3c546b3483ef0709ceb83a137c6c91f81/assets/images/favicon.png -------------------------------------------------------------------------------- /assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TechLanderThereCanOnlyBeOne/TechLander/fb5931a3c546b3483ef0709ceb83a137c6c91f81/assets/images/icon.png -------------------------------------------------------------------------------- /assets/images/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TechLanderThereCanOnlyBeOne/TechLander/fb5931a3c546b3483ef0709ceb83a137c6c91f81/assets/images/splash.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | plugins: [ 6 | ['module:react-native-dotenv', { 7 | 'moduleName': '@env', 8 | 'path': '.env', 9 | 'blacklist': null, 10 | 'whitelist': null, 11 | 'safe': false, 12 | 'allowUndefined': true 13 | }] 14 | ] 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /components/JobListing.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View, Text, StyleSheet, TouchableOpacity, Dimensions } from 'react-native'; 3 | import { Icon, Row } from 'native-base'; 4 | 5 | type JobListingProps = { 6 | history: []; 7 | listing: { 8 | company: { 9 | display_name: string; 10 | }; 11 | location: { 12 | display_name: string; 13 | area: []; 14 | }; 15 | redirect_url: string; 16 | description: string; 17 | title: string; 18 | created: string; 19 | }; 20 | tagStyles: { strong: { fontWeight: 'bold' } }; 21 | }; 22 | 23 | const JobListing = (props: JobListingProps) => { 24 | const listing = props.listing; 25 | const filteredTitle = listing['title'].replace(//g, '').replace(/<\/?strong\s*>/g, ''); 26 | const filteredDescription = listing['description'] 27 | .replace(//g, '') 28 | .replace(/<\/?strong\s*>/g, ''); 29 | return ( 30 | 31 | 32 | {filteredTitle} 33 | 34 | 35 | Company 36 | {listing['company']['display_name']} 37 | 38 | 39 | Location 40 | {listing['location']['display_name']} 41 | 42 | 43 | Posted 44 | {listing['created']} 45 | 46 | 47 | {filteredDescription} 48 | 49 | 50 | ); 51 | }; 52 | 53 | export default JobListing; 54 | 55 | const styles = StyleSheet.create({ 56 | container: { 57 | paddingTop: 15, 58 | flexDirection: 'row', 59 | margin: 20, 60 | }, 61 | listingContainer: { 62 | flexDirection: 'column', 63 | marginTop: 20, 64 | paddingTop: 20, 65 | paddingLeft: 30, 66 | paddingRight: 30, 67 | borderTopColor: '#7a42f4', 68 | borderTopWidth: 1, 69 | }, 70 | resultsContainer: { 71 | flexDirection: 'column', 72 | flexGrow: 1, 73 | marginBottom: 10, 74 | }, 75 | techListItem: { 76 | backgroundColor: '#7a42f4', 77 | borderRadius: 15, 78 | marginRight: 5, 79 | marginBottom: 5, 80 | padding: 5, 81 | flexDirection: 'row', 82 | }, 83 | bold: { 84 | fontWeight: 'bold', 85 | }, 86 | listingLine: { 87 | flexDirection: 'row', 88 | }, 89 | }); 90 | 91 | const styled = StyleSheet.create({ 92 | container: { 93 | flex: 1, 94 | backgroundColor: '#7fffd4', 95 | }, 96 | }); 97 | -------------------------------------------------------------------------------- /components/__tests__/component_test.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | 4 | // import component to test 5 | // import { MonoText } from '../StyledText'; 6 | 7 | it(`renders correctly`, () => { 8 | const tree = renderer.create(Snapshot test!).toJSON(); 9 | 10 | expect(tree).toMatchSnapshot(); 11 | }); 12 | -------------------------------------------------------------------------------- /hooks/useCachedResources.ts: -------------------------------------------------------------------------------- 1 | import { Ionicons } from "@expo/vector-icons"; 2 | import * as Font from "expo-font"; 3 | import * as SplashScreen from "expo-splash-screen"; 4 | import * as React from "react"; 5 | 6 | export default function useCachedResources() { 7 | const [isLoadingComplete, setLoadingComplete] = React.useState(false); 8 | 9 | // Load any resources or data that we need prior to rendering the app 10 | React.useEffect(() => { 11 | async function loadResourcesAndDataAsync() { 12 | try { 13 | SplashScreen.preventAutoHideAsync(); 14 | 15 | // Load fonts 16 | await Font.loadAsync({ 17 | Roboto: require('native-base/Fonts/Roboto.ttf'), 18 | Roboto_medium: require('native-base/Fonts/Roboto_medium.ttf'), 19 | }); 20 | } catch (e) { 21 | // We might want to provide this error information to an error reporting service 22 | console.warn(e); 23 | } finally { 24 | setLoadingComplete(true); 25 | SplashScreen.hideAsync(); 26 | } 27 | } 28 | 29 | loadResourcesAndDataAsync(); 30 | }, []); 31 | 32 | return isLoadingComplete; 33 | } 34 | -------------------------------------------------------------------------------- /hooks/useJobQuery.ts: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | // hook for element from result array 3 | export default response => { 4 | const [title, setTitle] = useState(response.title); 5 | const [company, setCompany] = useState(response['company']['display-name']); 6 | const [created, setCreated] = useState(response['created']); 7 | const [location, setLocation] = useState(response["location"]["display-name"]); 8 | const [description, setDescription] = useState(response["description"]); 9 | const [url, setURL] = useState(response["redirect_url"]); 10 | return { title, company, created, location, description, url} 11 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "node_modules/expo/AppEntry.js", 3 | "scripts": { 4 | "start": "concurrently \"nodemon server/server.js\" \"expo start\"", 5 | "android": "expo start --android", 6 | "ios": "expo start --ios", 7 | "web": "expo start --web", 8 | "eject": "expo eject", 9 | "test": "jest --watchAll" 10 | }, 11 | "jest": { 12 | "preset": "jest-expo" 13 | }, 14 | "dependencies": { 15 | "@expo/vector-icons": "^10.0.0", 16 | "@react-native-community/masked-view": "0.1.10", 17 | "@react-navigation/bottom-tabs": "^5.6.1", 18 | "@react-navigation/native": "^5.6.1", 19 | "@react-navigation/stack": "^5.6.2", 20 | "bcrypt": "^5.0.0", 21 | "concurrently": "^5.3.0", 22 | "cookie-parser": "^1.4.5", 23 | "dotenv": "^8.2.0", 24 | "expo": "~38.0.8", 25 | "expo-asset": "~8.1.7", 26 | "expo-constants": "~9.1.1", 27 | "expo-font": "~8.2.1", 28 | "expo-linking": "^1.0.1", 29 | "expo-splash-screen": "~0.5.0", 30 | "expo-status-bar": "^1.0.2", 31 | "expo-web-browser": "~8.3.1", 32 | "express": "^4.17.1", 33 | "native-base": "^2.13.14", 34 | "nodemon": "^2.0.4", 35 | "pg": "^8.3.3", 36 | "react": "~16.11.0", 37 | "react-dom": "~16.11.0", 38 | "react-native": "https://github.com/expo/react-native/archive/sdk-38.0.2.tar.gz", 39 | "react-native-dotenv": "^2.4.0", 40 | "react-native-gesture-handler": "~1.6.0", 41 | "react-native-safe-area-context": "~3.0.7", 42 | "react-native-screens": "~2.9.0", 43 | "react-native-web": "~0.11.7", 44 | "react-router-native": "^5.2.0", 45 | "rn-fetch-blob": "^0.12.0" 46 | }, 47 | "devDependencies": { 48 | "@babel/core": "^7.8.6", 49 | "@types/react": "~16.9.23", 50 | "@types/react-native": "~0.62.13", 51 | "babel-preset-expo": "~8.1.0", 52 | "jest-expo": "~38.0.0", 53 | "typescript": "~3.9.5" 54 | }, 55 | "private": true 56 | } 57 | -------------------------------------------------------------------------------- /screens/JobPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { 3 | View, 4 | Text, 5 | TouchableOpacity, 6 | TextInput, 7 | StyleSheet, 8 | } from "react-native"; 9 | 10 | const JobPage = ({ history }) => { 11 | const [email, setEmail] = useState(""); 12 | const [password, setPassword] = useState(""); 13 | const handleEmail = (text: string) => { 14 | setEmail(text); 15 | }; 16 | const handlePassword = (text: string) => { 17 | setPassword(text); 18 | }; 19 | const login = (email: string, pass: string) => { 20 | alert("email: " + email + " password: " + pass); 21 | }; 22 | 23 | return ( 24 | 25 | TechLander 26 | 27 | 35 | 36 | 44 | 45 | history.push("/jobpage")} //insert if conditional functionality later to see if credentials correct then route to jobs else alert message 48 | > 49 | Login 50 | 51 | 52 | history.push("/signup")} 55 | > 56 | Sign Up 57 | 58 | 59 | 60 | ); 61 | }; 62 | 63 | export default JobPage; 64 | 65 | const styles = StyleSheet.create({ 66 | container: { 67 | paddingTop: 23, 68 | }, 69 | signUpButton: { 70 | backgroundColor: "#7a42f4", 71 | margin: 15, 72 | height: 25, 73 | marginTop: 0, 74 | }, 75 | input: { 76 | margin: 15, 77 | height: 25, 78 | borderColor: "#7a42f4", 79 | borderWidth: 1, 80 | width: 200, 81 | textAlign: "center", 82 | color: "black", 83 | }, 84 | submitButton: { 85 | backgroundColor: "#7a42f4", 86 | margin: 15, 87 | height: 25, 88 | marginBottom: 5, 89 | }, 90 | submitButtonText: { 91 | color: "white", 92 | textAlign: "center", 93 | textAlignVertical: "center", 94 | marginTop: 3, 95 | }, 96 | }); 97 | 98 | const styled = StyleSheet.create({ 99 | container: { 100 | flex: 1, 101 | alignItems: "center", 102 | justifyContent: "center", 103 | backgroundColor: "#7fffd4", 104 | }, 105 | title: { 106 | fontSize: 20, 107 | fontWeight: "bold", 108 | color: "black", 109 | }, 110 | }); 111 | -------------------------------------------------------------------------------- /screens/Login.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { 3 | View, 4 | Text, 5 | TouchableOpacity, 6 | TextInput, 7 | StyleSheet, 8 | } from "react-native"; 9 | 10 | const Login = ({ history }) => { 11 | const [email, setEmail] = useState(""); 12 | const [password, setPassword] = useState(""); 13 | const handleEmail = (text: string) => { 14 | setEmail(text); 15 | }; 16 | const handlePassword = (text: string) => { 17 | setPassword(text); 18 | }; 19 | const login = (email: string, pass: string) => { 20 | alert("email: " + email + " password: " + pass); 21 | }; 22 | const loginBody = { username: email, password }; 23 | return ( 24 | 25 | TechLander 26 | 27 | 36 | 37 | 46 | 47 | 50 | fetch("(INSERT IP HERE)/login", { 51 | method: "POST", 52 | headers: { "Content-Type": "application/json" }, 53 | body: JSON.stringify(loginBody), 54 | }) 55 | .then((response) => response.text()) 56 | .then((text) => JSON.parse(text)) 57 | .then((data) => { 58 | if (data.check) { 59 | history.push("/jobpage"); 60 | // data.jobs is the the array of jobs stored in db 61 | } else { 62 | alert("Incorrect password or username"); 63 | } 64 | }) 65 | .catch((err) => console.log(err)) 66 | } //insert if conditional functionality later to see if credentials correct then route to jobs else alert message 67 | > 68 | Login 69 | 70 | 71 | history.push("/signup")} 74 | > 75 | Sign Up 76 | 77 | 78 | 79 | ); 80 | }; 81 | 82 | export default Login; 83 | 84 | const styles = StyleSheet.create({ 85 | container: { 86 | paddingTop: 23, 87 | }, 88 | signUpButton: { 89 | backgroundColor: "#7a42f4", 90 | margin: 15, 91 | height: 25, 92 | marginTop: 0, 93 | }, 94 | input: { 95 | margin: 15, 96 | height: 25, 97 | borderColor: "#7a42f4", 98 | borderWidth: 1, 99 | width: 200, 100 | textAlign: "center", 101 | color: "black", 102 | }, 103 | submitButton: { 104 | backgroundColor: "#7a42f4", 105 | margin: 15, 106 | height: 25, 107 | marginBottom: 5, 108 | }, 109 | submitButtonText: { 110 | color: "white", 111 | textAlign: "center", 112 | textAlignVertical: "center", 113 | marginTop: 3, 114 | }, 115 | }); 116 | 117 | const styled = StyleSheet.create({ 118 | container: { 119 | flex: 1, 120 | alignItems: "center", 121 | justifyContent: "center", 122 | backgroundColor: "#7fffd4", 123 | paddingBottom: 200, 124 | }, 125 | title: { 126 | fontSize: 20, 127 | fontWeight: "bold", 128 | color: "gray", 129 | }, 130 | }); 131 | -------------------------------------------------------------------------------- /screens/NotFoundScreen.tsx: -------------------------------------------------------------------------------- 1 | import { StackScreenProps } from "@react-navigation/stack"; 2 | import * as React from "react"; 3 | import { StyleSheet, Text, TouchableOpacity, View } from "react-native"; 4 | 5 | import { RootStackParamList } from "../types"; 6 | 7 | export default function NotFoundScreen({ 8 | navigation, 9 | }: StackScreenProps) { 10 | return ( 11 | 12 | This screen doesn't exist. 13 | navigation.replace("Root")} 15 | style={styles.link} 16 | > 17 | Go to home screen! 18 | 19 | 20 | ); 21 | } 22 | 23 | const styles = StyleSheet.create({ 24 | container: { 25 | flex: 1, 26 | backgroundColor: "#fff", 27 | alignItems: "center", 28 | justifyContent: "center", 29 | padding: 20, 30 | }, 31 | title: { 32 | fontSize: 20, 33 | fontWeight: "bold", 34 | }, 35 | link: { 36 | marginTop: 15, 37 | paddingVertical: 15, 38 | }, 39 | linkText: { 40 | fontSize: 14, 41 | color: "#2e78b7", 42 | }, 43 | }); 44 | -------------------------------------------------------------------------------- /screens/SearchContainer.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import JobListing from '../components/JobListing'; 3 | import { 4 | View, 5 | Text, 6 | TouchableOpacity, 7 | TextInput, 8 | StyleSheet, 9 | GestureResponderEvent, 10 | SafeAreaView, 11 | FlatList, 12 | } from 'react-native'; 13 | import { Icon, Row } from 'native-base'; 14 | import { APP_ID, APP_KEY } from '@env'; 15 | 16 | import { initialWindowMetrics } from 'react-native-safe-area-context'; 17 | import { ScrollView } from 'react-native-gesture-handler'; 18 | // import { uuid } from 'uuidv4'; 19 | 20 | type SearchContainerProps = { 21 | history: []; 22 | }; 23 | 24 | type state = { 25 | tech: string[]; 26 | currentTech: string; 27 | location: string; 28 | addQuery: string; 29 | subtractQuery: string; 30 | queriedListings: [{}]; 31 | listing: { 32 | company: { display_name: string }; 33 | location: { display_name: string; area: string[] }; 34 | redirect_url: string; 35 | description: string; 36 | title: string; 37 | created: string; 38 | }; 39 | }; 40 | 41 | const SearchContainer = (props: SearchContainerProps) => { 42 | const { history } = props; 43 | 44 | const [currentTech, updateCurrentTech] = useState(''); 45 | const [tech, addTech] = useState([]); 46 | const [location, setLocation] = useState(''); 47 | const [addQuery, setAddQuery] = useState(''); 48 | const [subtractQuery, setSubtractQuery] = useState(''); 49 | 50 | // queried listings is returned as as result array 51 | // Dummy data 52 | const [queriedListings, setQueriedJobListings] = useState([]); 53 | 54 | // fetch job listings when queries are added 55 | useEffect(() => { 56 | fetchListing(); 57 | console.log('useeffect fired'); 58 | }, [addQuery]); 59 | // fetch job listings when queries are subtracted 60 | useEffect(() => { 61 | fetchListing(); 62 | console.log('useeffect fired'); 63 | }, [subtractQuery]); 64 | 65 | const handleTechChange = (text: any) => { 66 | updateCurrentTech(text); 67 | }; 68 | 69 | const handleLocationInput = (text: string) => { 70 | setLocation(text); 71 | }; 72 | 73 | const handleLocationSubmit = () => { 74 | let modText = location.split(' ').join('%20'); 75 | modText = modText.split(',').join('%2C'); 76 | console.log(modText); 77 | setLocation(modText); 78 | // send location as query 79 | }; 80 | 81 | const handleTechStack = () => { 82 | if (currentTech.length > 0) { 83 | let initial: any = [...tech, currentTech]; 84 | addTech(initial); 85 | updateCurrentTech(''); 86 | } 87 | // add the most recent item into search query 88 | let newTechStack = tech.concat(currentTech); 89 | const addQueryString = newTechStack.join('%20'); 90 | // optional set state 91 | setAddQuery(addQueryString); 92 | // send query 93 | }; 94 | 95 | const handleDeleteTech = (e: any) => { 96 | // console.log(e); 97 | const idx = tech.indexOf(e); 98 | if (idx > -1) { 99 | tech.splice(idx, 1); 100 | } 101 | console.log(tech); 102 | handleTechStack(); 103 | const subtractQueryString = tech.join('%20'); 104 | // optional set state 105 | setSubtractQuery(subtractQueryString); 106 | // send query 107 | }; 108 | 109 | // fetch job listings with adzuna API 110 | // need to add APP_ID & APP_KEY to .env 111 | const fetchListing = () => { 112 | if (addQuery.length > 0 && location.length > 0) { 113 | let queryString = `http://api.adzuna.com/v1/api/jobs/us/search/1?app_id=${APP_ID}&app_key=${APP_KEY}&results_per_page=10&what=${addQuery}&where=${location}&distance=30`; 114 | console.log('queryString', queryString); 115 | fetch(queryString) 116 | .then((res: any) => res.json()) 117 | .then((data: any) => { 118 | console.log('jobs: ', data); 119 | let jobResults: any = [...data.results]; 120 | console.log('results'); 121 | setQueriedJobListings(jobResults); 122 | }) 123 | .catch((error: string) => console.log('error in fetch', error)); 124 | } 125 | }; 126 | 127 | // const uniqueKey = uuid(); 128 | return ( 129 | 130 | 131 | 132 | 133 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | {tech.map((techItem) => ( 163 | 164 | handleDeleteTech(techItem)}> 165 | {techItem} x 166 | 167 | 168 | ))} 169 | 170 | Search Results 171 | } 174 | /> 175 | 176 | 177 | ); 178 | }; 179 | 180 | export default SearchContainer; 181 | 182 | const styles = StyleSheet.create({ 183 | container: { 184 | flexDirection: 'row', 185 | margin: 10, 186 | }, 187 | title: { 188 | fontSize: 20, 189 | marginTop: 30, 190 | alignSelf: 'center', 191 | }, 192 | header: { 193 | fontSize: 14, 194 | fontWeight: 'bold', 195 | textTransform: 'uppercase', 196 | marginBottom: 10, 197 | marginLeft: 30, 198 | }, 199 | input: { 200 | marginLeft: 20, 201 | marginRight: 20, 202 | height: 35, 203 | borderColor: '#7a42f4', 204 | borderWidth: 1, 205 | width: 300, 206 | textAlign: 'center', 207 | color: 'black', 208 | }, 209 | locationButton: { 210 | backgroundColor: '#7a42f4', 211 | margin: 15, 212 | height: 25, 213 | marginBottom: 5, 214 | width: 100, 215 | marginLeft: 15, 216 | }, 217 | locationText: { 218 | color: 'white', 219 | textAlign: 'center', 220 | textAlignVertical: 'center', 221 | marginTop: 3, 222 | }, 223 | addText: { 224 | color: 'white', 225 | textAlign: 'center', 226 | textAlignVertical: 'center', 227 | marginTop: 15, 228 | }, 229 | addButton: { 230 | marginTop: 5, 231 | marginLeft: 0, 232 | justifyContent: 'center', 233 | }, 234 | listContainer: { 235 | flexDirection: 'row', 236 | flexWrap: 'wrap', 237 | flexGrow: 0, 238 | marginRight: 30, 239 | marginLeft: 30, 240 | marginBottom: 30, 241 | marginTop: 20, 242 | }, 243 | resultsContainer: { 244 | flexDirection: 'column', 245 | flexGrow: 1, 246 | marginBottom: 10, 247 | }, 248 | techListItem: { 249 | backgroundColor: '#7a42f4', 250 | borderRadius: 15, 251 | marginRight: 5, 252 | marginBottom: 5, 253 | padding: 5, 254 | flexDirection: 'row', 255 | }, 256 | deleteTechItem: { 257 | marginRight: 5, 258 | marginLeft: 5, 259 | color: 'white', 260 | fontWeight: 'bold', 261 | }, 262 | }); 263 | 264 | const styled = StyleSheet.create({ 265 | container: { 266 | flex: 1, 267 | backgroundColor: '#7fffd4', 268 | paddingTop: 20, 269 | }, 270 | }); 271 | -------------------------------------------------------------------------------- /screens/SignUp.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { View, Text, TouchableOpacity, TextInput, StyleSheet } from 'react-native'; 3 | 4 | const SignUp = ({ history }) => { 5 | const [email, setEmail] = useState(''); 6 | const [password1, setPassword] = useState(''); 7 | const [password2, setPassword2] = useState(''); 8 | const handleEmail = (text: string) => { 9 | setEmail(text); 10 | }; 11 | const handlePassword = (text: string) => { 12 | setPassword(text); 13 | }; 14 | const handlePassword2 = (text: string) => { 15 | if (password1 === text) setPassword2(text); 16 | }; 17 | const signUpBody = { username: email, password: password2 }; 18 | return ( 19 | 20 | Registration 21 | 22 | 31 | 32 | 41 | 42 | 51 | 52 | { 55 | if (password2) { 56 | fetch('http://10.0.1.22:19000/signUp', { 57 | method: 'POST', 58 | headers: { 'Content-Type': 'application/json' }, 59 | body: JSON.stringify(signUpBody), 60 | }) 61 | .then((response) => response.text()) 62 | .then((data) => { 63 | if (data) history.push('/'); 64 | }) 65 | .catch((err) => console.log(err)); 66 | } else alert("Passwords don't match"); 67 | }} //insert functionality to store create account later 68 | > 69 | Create Account 70 | 71 | 72 | history.push('/')} //insert functionality use github oauth later 75 | > 76 | Sign Up With Github 77 | 78 | 79 | 80 | ); 81 | }; 82 | 83 | export default SignUp; 84 | 85 | const styles = StyleSheet.create({ 86 | container: { 87 | paddingTop: 23, 88 | }, 89 | signUpButton: { 90 | backgroundColor: '#7a42f4', 91 | margin: 15, 92 | height: 25, 93 | marginTop: 0, 94 | }, 95 | input: { 96 | margin: 15, 97 | height: 25, 98 | borderColor: '#7a42f4', 99 | borderWidth: 1, 100 | width: 200, 101 | textAlign: 'center', 102 | color: 'black', 103 | }, 104 | submitButton: { 105 | backgroundColor: '#7a42f4', 106 | margin: 15, 107 | height: 25, 108 | marginBottom: 5, 109 | }, 110 | submitButtonText: { 111 | color: 'white', 112 | textAlign: 'center', 113 | textAlignVertical: 'center', 114 | marginTop: 3, 115 | }, 116 | }); 117 | 118 | const styled = StyleSheet.create({ 119 | container: { 120 | flex: 1, 121 | alignItems: 'center', 122 | // justifyContent: "center", 123 | paddingTop: 80, 124 | backgroundColor: '#7fffd4', 125 | }, 126 | title: { 127 | fontSize: 20, 128 | fontWeight: 'bold', 129 | color: 'black', 130 | }, 131 | }); 132 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "jsx": "react-native", 5 | "lib": ["dom", "esnext"], 6 | "moduleResolution": "node", 7 | "noEmit": true, 8 | "skipLibCheck": true, 9 | "resolveJsonModule": true, 10 | "strict": true, 11 | "noImplicitAny": false, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /types.tsx: -------------------------------------------------------------------------------- 1 | 2 | --------------------------------------------------------------------------------