Skip to content

Commit cfc2fdd

Browse files
author
Stephany-Doris
committed
react native jobs app initial setup
1 parent b6f43e4 commit cfc2fdd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+1636
-418
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ npm-debug.*
88
*.key
99
*.mobileprovision
1010
*.orig.*
11+
.env
1112
web-build/
1213

1314
# macOS

app/_layout.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Stack } from "expo-router";
2+
import { useCallback } from "react";
3+
import { useFonts } from "expo-font";
4+
import * as SplashScreen from "expo-splash-screen";
5+
6+
SplashScreen.preventAutoHideAsync();
7+
8+
const Layout = () => {
9+
const [fontsLoaded] = useFonts({
10+
DMBold: require("../assets/fonts/DMSans-Bold.ttf"),
11+
DMMedium: require("../assets/fonts/DMSans-Medium.ttf"),
12+
DMRegular: require("../assets/fonts/DMSans-Regular.ttf"),
13+
});
14+
15+
const onLayoutRootView = useCallback(async () => {
16+
if (fontsLoaded) {
17+
await SplashScreen.hideAsync();
18+
}
19+
}, [fontsLoaded]);
20+
21+
if (!fontsLoaded) return null;
22+
23+
return <Stack onLayout={onLayoutRootView} />;
24+
};
25+
26+
export default Layout;

app/index.js

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { View, ScrollView, SafeAreaView, Text } from "react-native";
2+
import { useState } from "react";
3+
import { Stack, useRouter } from "expo-router";
4+
5+
import {
6+
Nearbyjobs,
7+
Popularjobs,
8+
ScreenHeaderBtn,
9+
Welcome,
10+
} from "../components";
11+
import { COLORS, icons, images, SIZES } from "../constants";
12+
13+
const Home = () => {
14+
const [searchTerm, setSearchTerm] = useState("");
15+
const router = useRouter();
16+
17+
return (
18+
<SafeAreaView style={{ flex: 1, backgroundColor: COLORS.lightWhite }}>
19+
<Stack.Screen
20+
options={{
21+
headerStyle: { backgroundColor: COLORS.lightWhite },
22+
headerShadowVisible: false,
23+
headerTitle: "",
24+
headerLeft: () => (
25+
<ScreenHeaderBtn iconUrl={icons.menu} dimension="60%" />
26+
),
27+
headerRight: () => (
28+
<ScreenHeaderBtn iconUrl={images.profile} dimension="100%" />
29+
),
30+
}}
31+
/>
32+
<ScrollView showsVerticalScrollIndicator={false}>
33+
<View style={{ flex: 1, padding: SIZES.medium }}>
34+
<Welcome
35+
searchTerm={searchTerm}
36+
setSearchTerm={setSearchTerm}
37+
handleClick={() => {
38+
if (searchTerm) {
39+
router.push(`/search/${searchTerm}`);
40+
}
41+
}}
42+
/>
43+
<Popularjobs />
44+
<Nearbyjobs />
45+
</View>
46+
</ScrollView>
47+
</SafeAreaView>
48+
);
49+
};
50+
51+
export default Home;

app/job-details/[id].js

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import React, { useCallback, useState } from "react";
2+
import {
3+
Text,
4+
View,
5+
ScrollView,
6+
ActivityIndicator,
7+
RefreshControl,
8+
SafeAreaView,
9+
} from "react-native";
10+
import { Stack, useRouter, useSearchParams } from "expo-router";
11+
12+
import {
13+
Company,
14+
JobAbout,
15+
JobFooter,
16+
JobTabs,
17+
ScreenHeaderBtn,
18+
Specifics,
19+
} from "../../components";
20+
import { COLORS, SIZES, icons } from "../../constants";
21+
import useFetch from "../../hooks/useFetchHook";
22+
23+
const tabs = ["About", "Qualifications", "Responsibilities"];
24+
25+
const JobDetails = () => {
26+
const [refreshing, setRefreshing] = useState(false);
27+
const [activeTab, setActiveTab] = useState(tabs[0]);
28+
const params = useSearchParams();
29+
const router = useRouter();
30+
const { data, isLoading, error, refetch } = useFetch("job-details", {
31+
job_id: params.id,
32+
});
33+
34+
const onRefresh = () => {};
35+
36+
const displayTabContent = () => {
37+
switch (activeTab) {
38+
case "Qualifications":
39+
return (
40+
<Specifics
41+
title="Qualifications"
42+
points={data[0].job_highlights?.Qualifications ?? ["N/A"]}
43+
/>
44+
);
45+
case "About":
46+
return (
47+
<JobAbout info={data[0].job_description ?? "No data provided"} />
48+
);
49+
case "Responsibilities":
50+
return (
51+
<Specifics
52+
title="Responsibilities"
53+
points={data[0].job_highlights?.Responsibilities ?? ["N/A"]}
54+
/>
55+
);
56+
default:
57+
return null;
58+
}
59+
};
60+
61+
return (
62+
<SafeAreaView style={{ flex: 1, backgroundColor: COLORS.lightWhite }}>
63+
<Stack.Screen
64+
options={{
65+
headerStyle: { backgroundColor: COLORS.lightWhite },
66+
headerShadowVisible: false,
67+
headerBackVisible: false,
68+
headerLeft: () => (
69+
<ScreenHeaderBtn
70+
iconUrl={icons.left}
71+
dimension="60%"
72+
handlePress={() => router.back()}
73+
/>
74+
),
75+
headerRight: () => (
76+
<ScreenHeaderBtn iconUrl={icons.share} dimension="60%" />
77+
),
78+
headerTitle: "",
79+
}}
80+
/>
81+
<>
82+
<ScrollView
83+
showsVerticalScrollIndicator={false}
84+
refreshControl={
85+
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
86+
}
87+
>
88+
{isLoading ? (
89+
<ActivityIndicator size="large" color={COLORS.primary} />
90+
) : error ? (
91+
<Text>Something went wrong!</Text>
92+
) : data?.length === 0 ? (
93+
<Text>No data</Text>
94+
) : (
95+
<View style={{ padding: SIZES.medium, paddingBottom: 100 }}>
96+
<Company
97+
companyLogo={data[0].employer_logo}
98+
jobTitle={data[0].job_title}
99+
companyName={data[0].employer_name}
100+
location={data[0].job_country}
101+
/>
102+
<JobTabs
103+
tabs={tabs}
104+
activeTab={activeTab}
105+
setActiveTab={setActiveTab}
106+
/>
107+
108+
{displayTabContent()}
109+
</View>
110+
)}
111+
</ScrollView>
112+
<JobFooter
113+
url={
114+
data[0]?.job_google_link ??
115+
"https://careers.google.com/jobs/results/"
116+
}
117+
/>
118+
</>
119+
</SafeAreaView>
120+
);
121+
};
122+
123+
export default JobDetails;

app/search/[id].js

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import React, { useEffect, useState } from "react";
2+
import {
3+
ActivityIndicator,
4+
FlatList,
5+
Image,
6+
TouchableOpacity,
7+
View,
8+
} from "react-native";
9+
import { Stack, useRouter, useSearchParams } from "expo-router";
10+
import { Text, SafeAreaView } from "react-native";
11+
import axios from "axios";
12+
13+
import { ScreenHeaderBtn, NearbyJobCard } from "../../components";
14+
import { COLORS, icons, SIZES } from "../../constants";
15+
import styles from "../../styles/search";
16+
17+
import { RAPID_API_KEY } from "@env";
18+
const rapidAPIKey = RAPID_API_KEY;
19+
20+
const JobSearch = () => {
21+
const params = useSearchParams();
22+
const router = useRouter();
23+
24+
const [searchResult, setSearchResult] = useState([]);
25+
const [searchLoader, setSearchLoader] = useState(false);
26+
const [searchError, setSearchError] = useState(null);
27+
const [page, setPage] = useState(1);
28+
29+
const handleSearch = async () => {
30+
setSearchLoader(true);
31+
setSearchResult([]);
32+
33+
try {
34+
const options = {
35+
method: "GET",
36+
url: `https://jsearch.p.rapidapi.com/search`,
37+
headers: {
38+
"X-RapidAPI-Key": rapidAPIKey,
39+
"X-RapidAPI-Host": "jsearch.p.rapidapi.com",
40+
},
41+
params: {
42+
query: params.id,
43+
page: page.toString(),
44+
},
45+
};
46+
47+
const response = await axios.request(options);
48+
setSearchResult(response.data.data);
49+
} catch (error) {
50+
setSearchError(error);
51+
console.log(error);
52+
} finally {
53+
setSearchLoader(false);
54+
}
55+
};
56+
57+
const handlePagination = (direction) => {
58+
if (direction === "left" && page > 1) {
59+
setPage(page - 1);
60+
handleSearch();
61+
} else if (direction === "right") {
62+
setPage(page + 1);
63+
handleSearch();
64+
}
65+
};
66+
67+
useEffect(() => {
68+
handleSearch();
69+
}, []);
70+
71+
return (
72+
<SafeAreaView style={{ flex: 1, backgroundColor: COLORS.lightWhite }}>
73+
<Stack.Screen
74+
options={{
75+
headerStyle: { backgroundColor: COLORS.lightWhite },
76+
headerShadowVisible: false,
77+
headerLeft: () => (
78+
<ScreenHeaderBtn
79+
iconUrl={icons.left}
80+
dimension="60%"
81+
handlePress={() => router.back()}
82+
/>
83+
),
84+
headerTitle: "",
85+
}}
86+
/>
87+
88+
<FlatList
89+
data={searchResult}
90+
renderItem={({ item }) => (
91+
<NearbyJobCard
92+
job={item}
93+
handleNavigate={() => router.push(`/job-details/${item.job_id}`)}
94+
/>
95+
)}
96+
keyExtractor={(item) => item.job_id}
97+
contentContainerStyle={{ padding: SIZES.medium, rowGap: SIZES.medium }}
98+
ListHeaderComponent={() => (
99+
<>
100+
<View style={styles.container}>
101+
<Text style={styles.searchTitle}>{params.id}</Text>
102+
<Text style={styles.noOfSearchedJobs}>Job Opportunities</Text>
103+
</View>
104+
<View style={styles.loaderContainer}>
105+
{searchLoader ? (
106+
<ActivityIndicator size="large" color={COLORS.primary} />
107+
) : (
108+
searchError && <Text>Oops something went wrong</Text>
109+
)}
110+
</View>
111+
</>
112+
)}
113+
ListFooterComponent={() => (
114+
<View style={styles.footerContainer}>
115+
<TouchableOpacity
116+
style={styles.paginationButton}
117+
onPress={() => handlePagination("left")}
118+
>
119+
<Image
120+
source={icons.chevronLeft}
121+
style={styles.paginationImage}
122+
resizeMode="contain"
123+
/>
124+
</TouchableOpacity>
125+
<View style={styles.paginationTextBox}>
126+
<Text style={styles.paginationText}>{page}</Text>
127+
</View>
128+
<TouchableOpacity
129+
style={styles.paginationButton}
130+
onPress={() => handlePagination("right")}
131+
>
132+
<Image
133+
source={icons.chevronRight}
134+
style={styles.paginationImage}
135+
resizeMode="contain"
136+
/>
137+
</TouchableOpacity>
138+
</View>
139+
)}
140+
/>
141+
</SafeAreaView>
142+
);
143+
};
144+
145+
export default JobSearch;

assets/adaptive-icon.png

17.1 KB
Loading

assets/favicon.png

1.43 KB
Loading

assets/fonts/DMSans-Bold.ttf

70.1 KB
Binary file not shown.

assets/fonts/DMSans-Medium.ttf

70 KB
Binary file not shown.

assets/fonts/DMSans-Regular.ttf

70.2 KB
Binary file not shown.

assets/icon.png

21.9 KB
Loading

assets/icons/amazon.png

1.95 KB
Loading

assets/icons/apple.png

1.45 KB
Loading

assets/icons/chevron-left.png

604 Bytes
Loading

assets/icons/chevron-right.png

629 Bytes
Loading

assets/icons/figma.png

1.49 KB
Loading

assets/icons/filter.png

1.44 KB
Loading

assets/icons/google.png

2.16 KB
Loading

assets/icons/heart-ol.png

1.62 KB
Loading

assets/icons/heart.png

791 Bytes
Loading

assets/icons/left.png

435 Bytes
Loading

assets/icons/linkedin.png

1015 Bytes
Loading

assets/icons/location.png

1.23 KB
Loading

assets/icons/menu.png

323 Bytes
Loading

assets/icons/microsoft.png

335 Bytes
Loading

assets/icons/search.png

1.86 KB
Loading

assets/icons/share.png

1000 Bytes
Loading

assets/icons/slack.png

1.77 KB
Loading

assets/icons/spotify.png

2.98 KB
Loading

assets/icons/twitch.png

743 Bytes
Loading

assets/icons/twitter.png

1.37 KB
Loading

assets/icons/wattpad.png

1.56 KB
Loading

assets/images/kemal.jpg

24 KB
Loading

assets/splash.png

46.2 KB
Loading

babel.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ module.exports = function (api) {
55
plugins: [
66
"@babel/plugin-proposal-export-namespace-from",
77
"react-native-reanimated/plugin",
8+
"module:react-native-dotenv",
89
require.resolve("expo-router/babel"),
910
],
1011
};

0 commit comments

Comments
 (0)