├── .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 |
--------------------------------------------------------------------------------