├── assets
├── icon.png
├── favicon.png
├── splash.png
├── icons
│ ├── left.png
│ ├── search.png
│ ├── share.png
│ ├── location.png
│ ├── chevron-left.png
│ ├── chevron-right.png
│ └── heart-outline.png
├── adaptive-icon.png
└── fonts
│ ├── Roboto-Bold.ttf
│ ├── Roboto-Medium.ttf
│ └── Roboto-Regular.ttf
├── app
├── _layout.js
├── index.js
├── details
│ └── [id].js
└── search
│ └── [id].js
├── babel.config.js
├── components
├── home
│ ├── popular-jobs.js
│ ├── my-jobs.js
│ └── search.js
├── index.js
├── shared
│ └── header-btn.js
├── tabs
│ ├── about.js
│ ├── qualification.js
│ ├── responsibility.js
│ └── footer.js
├── detailed
│ ├── job-tabs.js
│ └── job.js
└── cards
│ ├── job-card.js
│ └── my-job-card.js
├── constants
├── data.js
├── index.js
├── icons.js
└── theme.js
├── app.json
├── package.json
├── hook
└── useRequest.js
└── README.md
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samarbadriddin0v/hh-clone/HEAD/assets/icon.png
--------------------------------------------------------------------------------
/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samarbadriddin0v/hh-clone/HEAD/assets/favicon.png
--------------------------------------------------------------------------------
/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samarbadriddin0v/hh-clone/HEAD/assets/splash.png
--------------------------------------------------------------------------------
/assets/icons/left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samarbadriddin0v/hh-clone/HEAD/assets/icons/left.png
--------------------------------------------------------------------------------
/assets/icons/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samarbadriddin0v/hh-clone/HEAD/assets/icons/search.png
--------------------------------------------------------------------------------
/assets/icons/share.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samarbadriddin0v/hh-clone/HEAD/assets/icons/share.png
--------------------------------------------------------------------------------
/assets/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samarbadriddin0v/hh-clone/HEAD/assets/adaptive-icon.png
--------------------------------------------------------------------------------
/assets/icons/location.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samarbadriddin0v/hh-clone/HEAD/assets/icons/location.png
--------------------------------------------------------------------------------
/assets/fonts/Roboto-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samarbadriddin0v/hh-clone/HEAD/assets/fonts/Roboto-Bold.ttf
--------------------------------------------------------------------------------
/assets/fonts/Roboto-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samarbadriddin0v/hh-clone/HEAD/assets/fonts/Roboto-Medium.ttf
--------------------------------------------------------------------------------
/assets/fonts/Roboto-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samarbadriddin0v/hh-clone/HEAD/assets/fonts/Roboto-Regular.ttf
--------------------------------------------------------------------------------
/assets/icons/chevron-left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samarbadriddin0v/hh-clone/HEAD/assets/icons/chevron-left.png
--------------------------------------------------------------------------------
/assets/icons/chevron-right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samarbadriddin0v/hh-clone/HEAD/assets/icons/chevron-right.png
--------------------------------------------------------------------------------
/assets/icons/heart-outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samarbadriddin0v/hh-clone/HEAD/assets/icons/heart-outline.png
--------------------------------------------------------------------------------
/app/_layout.js:
--------------------------------------------------------------------------------
1 | import { Stack } from "expo-router";
2 |
3 | export default function Layout() {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function (api) {
2 | api.cache(true);
3 | return {
4 | presets: ["babel-preset-expo"],
5 | plugins: ["expo-router/babel"],
6 | };
7 | };
8 |
--------------------------------------------------------------------------------
/components/home/popular-jobs.js:
--------------------------------------------------------------------------------
1 | import { View, Text } from "react-native";
2 | import React from "react";
3 |
4 | export default function PopularJobs() {
5 | return (
6 |
7 | PopularJobs
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/constants/data.js:
--------------------------------------------------------------------------------
1 | export const filterJobTypes = [
2 | "Full-time",
3 | "Part-time",
4 | "Contract",
5 | "Temporary",
6 | "Internship",
7 | "Volunteer",
8 | "Remote",
9 | "Freelance",
10 | ];
11 |
12 | export const tabs = ["About", "Qualifications", "Responsibilities"];
13 |
--------------------------------------------------------------------------------
/constants/index.js:
--------------------------------------------------------------------------------
1 | // data
2 | import { filterJobTypes } from "./data";
3 | import { tabs } from "./data";
4 |
5 | // theme
6 | import { COLORS, SIZES, FONTS, SHADOWS } from "./theme";
7 |
8 | // icon
9 | import icons from "./icons";
10 |
11 | export { filterJobTypes, COLORS, SIZES, FONTS, SHADOWS, icons, tabs };
12 |
--------------------------------------------------------------------------------
/constants/icons.js:
--------------------------------------------------------------------------------
1 | // icons
2 | import search from "../assets/icons/search.png";
3 | import heart from "../assets/icons/heart-outline.png";
4 | import left from "../assets/icons/left.png";
5 | import share from "../assets/icons/share.png";
6 | import location from "../assets/icons/location.png";
7 | import chevronLeft from "../assets/icons/chevron-left.png";
8 | import chevronRight from "../assets/icons/chevron-right.png";
9 |
10 | export default {
11 | search,
12 | heart,
13 | left,
14 | share,
15 | location,
16 | chevronLeft,
17 | chevronRight,
18 | };
19 |
--------------------------------------------------------------------------------
/components/index.js:
--------------------------------------------------------------------------------
1 | import Search from "./home/search";
2 | import MyJobs from "./home/my-jobs";
3 | import PopularJobs from "./home/popular-jobs";
4 | import HeaderBtn from "./shared/header-btn";
5 | import JobTabs from "./detailed/job-tabs";
6 | import Job from "./detailed/job";
7 | import About from "./tabs/about";
8 | import Qualification from "./tabs/qualification";
9 | import Responsibility from "./tabs/responsibility";
10 | import Footer from "./tabs/footer";
11 |
12 | export {
13 | Search,
14 | MyJobs,
15 | PopularJobs,
16 | HeaderBtn,
17 | JobTabs,
18 | Job,
19 | About,
20 | Qualification,
21 | Responsibility,
22 | Footer,
23 | };
24 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "scheme": "your-app-scheme",
4 | "name": "hh-clone",
5 | "slug": "hh-clone",
6 | "version": "1.0.0",
7 | "orientation": "portrait",
8 | "icon": "./assets/icon.png",
9 | "userInterfaceStyle": "light",
10 | "splash": {
11 | "image": "./assets/splash.png",
12 | "resizeMode": "contain",
13 | "backgroundColor": "#ffffff"
14 | },
15 | "assetBundlePatterns": ["**/*"],
16 | "ios": {
17 | "supportsTablet": true
18 | },
19 | "android": {
20 | "adaptiveIcon": {
21 | "foregroundImage": "./assets/adaptive-icon.png",
22 | "backgroundColor": "#ffffff"
23 | }
24 | },
25 | "web": {
26 | "favicon": "./assets/favicon.png"
27 | },
28 | "plugins": ["expo-router"]
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/components/shared/header-btn.js:
--------------------------------------------------------------------------------
1 | import { TouchableOpacity, StyleSheet, Image } from "react-native";
2 | import React from "react";
3 | import { COLORS, SIZES } from "../../constants";
4 |
5 | export default function HeaderBtn({ icon, onPress, dimensions }) {
6 | return (
7 |
8 |
9 |
10 | );
11 | }
12 |
13 | const styles = StyleSheet.create({
14 | container: {
15 | width: 40,
16 | height: 40,
17 | backgroundColor: COLORS.white,
18 | borderRadius: SIZES.small / 2,
19 | justifyContent: "center",
20 | alignItems: "center",
21 | },
22 | icon: (dimensions) => ({
23 | width: dimensions,
24 | height: dimensions,
25 | borderRadius: SIZES.small / 2,
26 | }),
27 | });
28 |
--------------------------------------------------------------------------------
/constants/theme.js:
--------------------------------------------------------------------------------
1 | export const COLORS = {
2 | primary: "#9B2C2C",
3 | secondary: "#63171B",
4 | tertiary: "#E53E3E",
5 |
6 | gray: "#83829A",
7 | gray2: "#C1C0C8",
8 |
9 | white: "#F3F4F8",
10 | lightWhite: "#FAFAFC",
11 | };
12 |
13 | export const SIZES = {
14 | xSmall: 10,
15 | small: 14,
16 | medium: 16,
17 | large: 18,
18 | xLarge: 20,
19 | };
20 |
21 | export const SHADOWS = {
22 | small: {
23 | shadowColor: "#000",
24 | shadowOffset: {
25 | width: 0,
26 | height: 2,
27 | },
28 | shadowOpacity: 0.25,
29 | shadowRadius: 3.84,
30 | elevation: 2,
31 | },
32 | medium: {
33 | shadowColor: "#000",
34 | shadowOffset: {
35 | width: 0,
36 | height: 2,
37 | },
38 | shadowOpacity: 0.25,
39 | shadowRadius: 5.84,
40 | elevation: 5,
41 | },
42 | };
43 |
44 | export const FONTS = {
45 | regular: "Roboto-Regular",
46 | medium: "Roboto-Medium",
47 | bold: "Roboto-Bold",
48 | };
49 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hh-clone",
3 | "version": "1.0.0",
4 | "main": "expo-router/entry",
5 | "scripts": {
6 | "start": "expo start",
7 | "android": "expo start --android",
8 | "ios": "expo start --ios",
9 | "web": "expo start --web"
10 | },
11 | "dependencies": {
12 | "axios": "^1.5.1",
13 | "expo": "~49.0.13",
14 | "expo-constants": "~14.4.2",
15 | "expo-font": "~11.4.0",
16 | "expo-linking": "~5.0.2",
17 | "expo-router": "^2.0.0",
18 | "expo-status-bar": "~1.6.0",
19 | "react": "18.2.0",
20 | "react-native": "0.72.5",
21 | "react-native-gesture-handler": "~2.12.0",
22 | "react-native-safe-area-context": "4.6.3",
23 | "react-native-screens": "~3.22.0"
24 | },
25 | "devDependencies": {
26 | "@babel/core": "^7.20.0"
27 | },
28 | "private": true,
29 | "overrides": {
30 | "metro": "0.76.7",
31 | "metro-resolver": "0.76.7"
32 | },
33 | "resolutions": {
34 | "metro": "0.76.7",
35 | "metro-resolver": "0.76.7"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/components/tabs/about.js:
--------------------------------------------------------------------------------
1 | import { View, Text, StyleSheet } from "react-native";
2 | import React from "react";
3 | import { COLORS, FONTS, SIZES } from "../../constants";
4 |
5 | export default function About({ info }) {
6 | return (
7 |
8 | About this job:
9 |
10 |
11 | {info}
12 |
13 |
14 | );
15 | }
16 |
17 | const styles = StyleSheet.create({
18 | container: {
19 | marginTop: SIZES.large,
20 | backgroundColor: COLORS.lightWhite,
21 | borderRadius: SIZES.medium,
22 | paddingHorizontal: SIZES.medium,
23 | },
24 | title: {
25 | fontSize: SIZES.large,
26 | color: COLORS.primary,
27 | fontFamily: FONTS.bold,
28 | },
29 | contentBox: {
30 | marginTop: SIZES.medium,
31 | },
32 | contentText: {
33 | fontSize: SIZES.small,
34 | color: COLORS.secondary,
35 | fontFamily: FONTS.regular,
36 | },
37 | });
38 |
--------------------------------------------------------------------------------
/hook/useRequest.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import axios from "axios";
3 |
4 | export default function useRequest(endpoint, query) {
5 | const [data, setData] = useState([]);
6 | const [isLoading, setIsLoading] = useState(false);
7 | const [error, setError] = useState(null);
8 |
9 | const option = {
10 | method: "GET",
11 | url: `https://jsearch.p.rapidapi.com/${endpoint}`,
12 | headers: {
13 | "X-RapidAPI-Key": "8e99652f73msh0d6b44b17e30ae3p165001jsna1eed97a1b9c",
14 | "X-RapidAPI-Host": "jsearch.p.rapidapi.com",
15 | },
16 | params: { ...query },
17 | };
18 |
19 | const fetchData = async () => {
20 | setIsLoading(true);
21 |
22 | try {
23 | const { data: res } = await axios.request(option);
24 | setData(res.data);
25 | } catch (error) {
26 | setError(error);
27 | console.log(error);
28 | } finally {
29 | setIsLoading(false);
30 | }
31 | };
32 |
33 | useEffect(() => {
34 | fetchData();
35 | }, []);
36 |
37 | const refetch = () => {
38 | setIsLoading(true);
39 | fetchData();
40 | };
41 |
42 | return { data, isLoading, error, refetch };
43 | }
44 |
--------------------------------------------------------------------------------
/app/index.js:
--------------------------------------------------------------------------------
1 | import { View, Text, SafeAreaView } from "react-native";
2 | import React, { useCallback } from "react";
3 | import { ScrollView } from "react-native-gesture-handler";
4 | import { MyJobs, PopularJobs, Search } from "../components";
5 | import { useFonts } from "expo-font";
6 | import * as SplashScreen from "expo-splash-screen";
7 | import { SIZES } from "../constants";
8 | import { Stack } from "expo-router";
9 |
10 | SplashScreen.preventAutoHideAsync();
11 |
12 | export default function Index() {
13 | const [fontsLoaded] = useFonts({
14 | "Roboto-Regular": require("../assets/fonts/Roboto-Regular.ttf"),
15 | "Roboto-Bold": require("../assets/fonts/Roboto-Bold.ttf"),
16 | "Roboto-Medium": require("../assets/fonts/Roboto-Medium.ttf"),
17 | });
18 |
19 | const onLayoutRootView = useCallback(async () => {
20 | if (fontsLoaded) {
21 | await SplashScreen.hideAsync();
22 | }
23 | }, [fontsLoaded]);
24 |
25 | if (!fontsLoaded) {
26 | return null;
27 | }
28 |
29 | return (
30 |
34 |
35 |
36 |
37 |
38 |
39 | {/* */}
40 |
41 |
42 |
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/components/tabs/qualification.js:
--------------------------------------------------------------------------------
1 | import { View, Text, StyleSheet } from "react-native";
2 | import React from "react";
3 | import { COLORS, FONTS, SIZES } from "../../constants";
4 |
5 | export default function Qualification({ info }) {
6 | return (
7 |
8 | Qualification:
9 |
10 |
11 | {info.map((item, index) => (
12 |
13 |
14 | {item}
15 |
16 | ))}
17 |
18 |
19 | );
20 | }
21 |
22 | const styles = StyleSheet.create({
23 | container: {
24 | marginTop: SIZES.large,
25 | backgroundColor: COLORS.lightWhite,
26 | borderRadius: SIZES.medium,
27 | paddingHorizontal: SIZES.medium,
28 | },
29 | title: {
30 | fontSize: SIZES.large,
31 | color: COLORS.primary,
32 | fontFamily: FONTS.bold,
33 | },
34 | contentBox: {
35 | marginVertical: SIZES.small,
36 | },
37 | infoWrapper: {
38 | flexDirection: "row",
39 | justifyContent: "flex-start",
40 | alignItems: "flex-start",
41 | marginVertical: SIZES.small / 1.25,
42 | },
43 | dot: {
44 | width: 6,
45 | height: 6,
46 | borderRadius: 6,
47 | backgroundColor: COLORS.gray2,
48 | marginTop: 6,
49 | },
50 | contentText: {
51 | fontSize: SIZES.medium - 2,
52 | color: COLORS.gray,
53 | fontFamily: FONTS.regular,
54 | marginLeft: SIZES.small,
55 | },
56 | });
57 |
--------------------------------------------------------------------------------
/components/tabs/responsibility.js:
--------------------------------------------------------------------------------
1 | import { View, Text, StyleSheet } from "react-native";
2 | import React from "react";
3 | import { COLORS, FONTS, SIZES } from "../../constants";
4 |
5 | export default function Responsibility({ info }) {
6 | return (
7 |
8 | Responsibility:
9 |
10 |
11 | {info.map((item, index) => (
12 |
13 |
14 | {item}
15 |
16 | ))}
17 |
18 |
19 | );
20 | }
21 |
22 | const styles = StyleSheet.create({
23 | container: {
24 | marginTop: SIZES.large,
25 | backgroundColor: COLORS.lightWhite,
26 | borderRadius: SIZES.medium,
27 | paddingHorizontal: SIZES.medium,
28 | },
29 | title: {
30 | fontSize: SIZES.large,
31 | color: COLORS.primary,
32 | fontFamily: FONTS.bold,
33 | },
34 | contentBox: {
35 | marginVertical: SIZES.small,
36 | },
37 | infoWrapper: {
38 | flexDirection: "row",
39 | justifyContent: "flex-start",
40 | alignItems: "flex-start",
41 | marginVertical: SIZES.small / 1.25,
42 | },
43 | dot: {
44 | width: 6,
45 | height: 6,
46 | borderRadius: 6,
47 | backgroundColor: COLORS.gray2,
48 | marginTop: 6,
49 | },
50 | contentText: {
51 | fontSize: SIZES.medium - 2,
52 | color: COLORS.gray,
53 | fontFamily: FONTS.regular,
54 | marginLeft: SIZES.small,
55 | },
56 | });
57 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
React Native Job Search App
2 |
3 | Job Search: Empowering Your Career Journey! Our cutting-edge mobile application developed using React Native seamlessly connects job seekers with their dream opportunities on both iOS and Android platforms. With user-friendly interfaces and advanced algorithms Catalyst Job Search offers personalized job recommendations real-time notifications and interactive features to streamline your job search process. Explore endless possibilities discover tailored job listings and embark on your career adventure with confidence. Your dream job is just a tap away
4 |
5 | 🚀 Demo
6 |
7 | [https://youtu.be/K270hhdGRTk?si=6HjLBLRjv4BLcq91](https://youtu.be/K270hhdGRTk?si=6HjLBLRjv4BLcq91)
8 |
9 | Project Screenshots:
10 |
11 |
12 |
13 |
14 |
15 | 🧐 Features
16 |
17 | Here're some of the project's best features:
18 |
19 | * Mobile application
20 | * IOS
21 | * Adnroid
22 | * Mobile developments
23 |
24 | 🛠️ Installation Steps:
25 |
26 | 1. Install all dependencies
27 |
28 | ```
29 | npm install | yarn install
30 | ```
31 |
32 | 2. Run application using expo metro
33 |
34 | ```
35 | npm start | yarn start
36 | ```
37 |
38 |
39 |
40 | 💻 Built with
41 |
42 | Technologies used in the project:
43 |
44 | * JavaScript
45 | * ReactJS
46 | * React Native
47 | * Expo
48 | * Expo router
49 | * React Navigation
50 | * Custom theme
51 |
--------------------------------------------------------------------------------
/components/detailed/job-tabs.js:
--------------------------------------------------------------------------------
1 | import {
2 | View,
3 | Text,
4 | StyleSheet,
5 | FlatList,
6 | TouchableOpacity,
7 | } from "react-native";
8 | import React from "react";
9 | import { COLORS, SIZES, tabs, SHADOWS, FONTS } from "../../constants";
10 |
11 | export default function JobTabs({ activeTab, setActiveTab }) {
12 | return (
13 |
14 | item}
20 | renderItem={({ item }) => (
21 | setActiveTab(item)}
24 | >
25 | {item}
26 |
27 | )}
28 | />
29 |
30 | );
31 | }
32 |
33 | const styles = StyleSheet.create({
34 | container: {
35 | marginTop: SIZES.medium,
36 | marginBottom: SIZES.small,
37 | },
38 | tabBtn: (activeTab, item) => ({
39 | paddingVertical: SIZES.medium,
40 | paddingHorizontal: SIZES.xLarge,
41 | backgroundColor: item === activeTab ? COLORS.primary : "#F3F4F8",
42 | borderRadius: SIZES.medium,
43 | marginLeft: 2,
44 | ...SHADOWS.medium,
45 | shadowColor: COLORS.white,
46 | }),
47 | tabText: (activeTab, item) => ({
48 | fontSize: SIZES.small,
49 | color: item === activeTab ? COLORS.gray2 : COLORS.primary,
50 | fontFamily: FONTS.medium,
51 | }),
52 | });
53 |
--------------------------------------------------------------------------------
/components/home/my-jobs.js:
--------------------------------------------------------------------------------
1 | import {
2 | View,
3 | Text,
4 | StyleSheet,
5 | ActivityIndicator,
6 | FlatList,
7 | } from "react-native";
8 | import React, { useState } from "react";
9 | import useRequest from "../../hook/useRequest";
10 | import { COLORS, FONTS, SIZES } from "../../constants";
11 | import MyJobCard from "../cards/my-job-card";
12 |
13 | export default function MyJobs() {
14 | const [selectedJob, setSelectedJob] = useState(null);
15 |
16 | const { data, isLoading, error } = useRequest("search", {
17 | query: "React native",
18 | page: "1",
19 | });
20 |
21 | return (
22 |
23 | Jobs for you
24 |
25 |
26 | {isLoading ? (
27 |
28 | ) : error ? (
29 | Something went wrong
30 | ) : (
31 | (
34 |
39 | )}
40 | keyExtractor={(item) => `job-${item.job_id}`}
41 | contentContainerStyle={{ columnGap: SIZES.medium }}
42 | scrollEnabled={false}
43 | nestedScrollEnabled={true}
44 | />
45 | )}
46 |
47 |
48 | );
49 | }
50 |
51 | const styles = StyleSheet.create({
52 | container: {
53 | marginTop: SIZES.xLarge,
54 | },
55 | title: {
56 | fontSize: SIZES.xLarge,
57 | fontFamily: FONTS.bold,
58 | color: COLORS.primary,
59 | },
60 | jobsContainer: {
61 | marginTop: SIZES.medium,
62 | },
63 | });
64 |
--------------------------------------------------------------------------------
/components/tabs/footer.js:
--------------------------------------------------------------------------------
1 | import {
2 | View,
3 | Text,
4 | StyleSheet,
5 | TouchableOpacity,
6 | Image,
7 | Linking,
8 | } from "react-native";
9 | import React from "react";
10 | import { COLORS, FONTS, SIZES, icons } from "../../constants";
11 |
12 | export default function Footer({ url }) {
13 | return (
14 |
15 |
16 |
21 |
22 | Linking.openURL(url)}
25 | >
26 | Apply
27 |
28 |
29 | );
30 | }
31 |
32 | const styles = StyleSheet.create({
33 | container: {
34 | position: "absolute",
35 | bottom: 0,
36 | left: 0,
37 | right: 0,
38 | padding: SIZES.small,
39 | backgroundColor: "#fff",
40 | justifyContent: "space-between",
41 | alignContent: "center",
42 | flexDirection: "row",
43 | },
44 | heartBtn: {
45 | width: 55,
46 | height: 55,
47 | borderWidth: 1,
48 | borderColor: COLORS.primary,
49 | borderRadius: SIZES.medium,
50 | justifyContent: "center",
51 | alignItems: "center",
52 | },
53 | heartBtnIcon: {
54 | width: "40%",
55 | height: "40%",
56 | tintColor: COLORS.primary,
57 | },
58 | applyBtn: {
59 | flex: 1,
60 | backgroundColor: COLORS.primary,
61 | height: "100%",
62 | justifyContent: "center",
63 | alignItems: "center",
64 | marginLeft: SIZES.medium,
65 | borderRadius: SIZES.medium,
66 | },
67 | applyBtnText: {
68 | color: COLORS.lightWhite,
69 | fontSize: SIZES.medium,
70 | fontFamily: FONTS.bold,
71 | },
72 | });
73 |
--------------------------------------------------------------------------------
/components/cards/job-card.js:
--------------------------------------------------------------------------------
1 | import { View, Text, TouchableOpacity, StyleSheet, Image } from "react-native";
2 | import React from "react";
3 | import { SIZES, SHADOWS, COLORS, FONTS } from "../../constants";
4 |
5 | export default function JobCard({ item, onPress }) {
6 | return (
7 |
8 |
9 |
17 |
18 |
19 |
20 | {item?.job_title}
21 | {item?.job_employment_type}
22 |
23 |
24 | );
25 | }
26 |
27 | const styles = StyleSheet.create({
28 | container: {
29 | flex: 1,
30 | justifyContent: "space-between",
31 | alignItems: "center",
32 | flexDirection: "row",
33 | padding: SIZES.medium,
34 | borderRadius: SIZES.small,
35 | backgroundColor: "#FFF",
36 | ...SHADOWS.medium,
37 | shadowColor: COLORS.white,
38 | },
39 | imgWrapper: {
40 | width: 50,
41 | height: 50,
42 | backgroundColor: COLORS.white,
43 | borderRadius: SIZES.medium,
44 | justifyContent: "center",
45 | alignItems: "center",
46 | },
47 | logoImage: {
48 | width: "70%",
49 | height: "70%",
50 | },
51 | textWrapper: {
52 | flex: 1,
53 | marginHorizontal: SIZES.medium,
54 | },
55 | jobName: {
56 | fontSize: SIZES.medium,
57 | fontFamily: FONTS.medium,
58 | color: COLORS.primary,
59 | },
60 | jobType: {
61 | fontSize: SIZES.small,
62 | marginTop: SIZES.small / 2,
63 | fontFamily: FONTS.regular,
64 | textTransform: "capitalize",
65 | color: COLORS.gray,
66 | },
67 | });
68 |
--------------------------------------------------------------------------------
/components/detailed/job.js:
--------------------------------------------------------------------------------
1 | import { View, Text, StyleSheet, Image } from "react-native";
2 | import React from "react";
3 | import { COLORS, FONTS, SIZES, icons } from "../../constants";
4 |
5 | export default function Job({ companyLogo, jobTitle, companyName, location }) {
6 | return (
7 |
8 |
9 |
17 |
18 |
19 |
20 | {jobTitle}
21 |
22 |
23 |
24 | {companyName}
25 |
26 |
31 | {location}
32 |
33 |
34 |
35 | );
36 | }
37 |
38 | const styles = StyleSheet.create({
39 | conatiner: {
40 | marginVertical: SIZES.medium,
41 | justifyContent: "center",
42 | alignItems: "center",
43 | },
44 | imgBox: {
45 | width: 80,
46 | height: 80,
47 | justifyContent: "center",
48 | alignItems: "center",
49 | backgroundColor: "#FFF",
50 | borderRadius: SIZES.large,
51 | },
52 | logoImage: {
53 | width: "80%",
54 | height: "80%",
55 | },
56 | jobTitleBox: {
57 | marginTop: SIZES.small,
58 | },
59 | jobTitle: {
60 | fontSize: SIZES.large,
61 | color: COLORS.primary,
62 | fontFamily: FONTS.bold,
63 | textAlign: "center",
64 | },
65 | companyInfoBox: {
66 | marginTop: SIZES.small / 2,
67 | flexDirection: "row",
68 | justifyContent: "center",
69 | alignItems: "center",
70 | },
71 | companyName: {
72 | fontSize: SIZES.medium - 2,
73 | color: COLORS.primary,
74 | fontFamily: FONTS.medium,
75 | },
76 | locationBox: {
77 | flexDirection: "row",
78 | justifyContent: "center",
79 | alignItems: "center",
80 | },
81 | locationImage: {
82 | width: 14,
83 | height: 14,
84 | tintColor: COLORS.gray,
85 | },
86 | locationName: {
87 | fontSize: SIZES.medium - 2,
88 | color: COLORS.gray,
89 | fontFamily: FONTS.regular,
90 | marginLeft: 2,
91 | },
92 | });
93 |
--------------------------------------------------------------------------------
/app/details/[id].js:
--------------------------------------------------------------------------------
1 | import {
2 | View,
3 | Text,
4 | ScrollView,
5 | ActivityIndicator,
6 | SafeAreaView,
7 | RefreshControl,
8 | } from "react-native";
9 | import React, { useCallback, useState } from "react";
10 | import { COLORS, SIZES, icons, tabs } from "../../constants";
11 | import { Stack, useGlobalSearchParams, useRouter } from "expo-router";
12 | import HeaderBtn from "../../components/shared/header-btn";
13 | import useRequest from "../../hook/useRequest";
14 | import {
15 | About,
16 | Footer,
17 | Job,
18 | JobTabs,
19 | Qualification,
20 | Responsibility,
21 | } from "../../components";
22 |
23 | export default function Details() {
24 | const params = useGlobalSearchParams();
25 | const router = useRouter();
26 |
27 | const [activeTab, setActiveTab] = useState(tabs[0]);
28 | const [refreshing, setRefreshing] = useState(false);
29 |
30 | const { data, isLoading, error, refetch } = useRequest("job-details", {
31 | job_id: params.id,
32 | });
33 |
34 | const onRefresh = useCallback(() => {
35 | setRefreshing(true);
36 | refetch();
37 | setRefreshing(false);
38 | }, []);
39 |
40 | const renderTabContent = () => {
41 | switch (activeTab) {
42 | case "About":
43 | return ;
44 | case "Qualifications":
45 | return (
46 |
49 | );
50 | case "Responsibilities":
51 | return (
52 |
55 | );
56 | default:
57 | return null;
58 | }
59 | };
60 |
61 | return (
62 |
63 | (
71 | router.back()}
75 | />
76 | ),
77 | headerRight: () => (
78 |
79 | ),
80 | }}
81 | />
82 | <>
83 |
87 | }
88 | >
89 | {isLoading ? (
90 |
91 | ) : error ? (
92 | Something went wrong
93 | ) : data.length === 0 ? (
94 | No data available
95 | ) : (
96 |
97 |
103 |
104 | {renderTabContent()}
105 |
106 | )}
107 |
108 |
109 |
115 | >
116 |
117 | );
118 | }
119 |
--------------------------------------------------------------------------------
/components/home/search.js:
--------------------------------------------------------------------------------
1 | import {
2 | View,
3 | Text,
4 | TextInput,
5 | TouchableOpacity,
6 | Image,
7 | FlatList,
8 | } from "react-native";
9 | import React, { useState } from "react";
10 | import { useRouter } from "expo-router";
11 | import { StyleSheet } from "react-native";
12 | import { COLORS, FONTS, SIZES, filterJobTypes, icons } from "../../constants";
13 |
14 | export default function Search() {
15 | const [activeJobType, setActiveJobType] = useState("Full-time");
16 | const [term, setTerm] = useState("");
17 |
18 | const router = useRouter();
19 |
20 | const onPress = (item) => {
21 | setActiveJobType(item);
22 | router.push(`/search/${item}`);
23 | };
24 |
25 | const onSearchPress = () => {
26 | if (term.trim().length === 0) return;
27 | router.push(`/search/${term}`);
28 | };
29 |
30 | return (
31 |
32 | {/* Search input container */}
33 |
34 |
35 | setTerm(text)}
40 | />
41 |
42 |
43 |
44 |
49 |
50 |
51 |
52 | {/* Filter container */}
53 |
54 | (
57 | onPress(item)}
60 | >
61 |
62 | {item}
63 |
64 |
65 | )}
66 | keyExtractor={(item) => `filter-job-${item}`}
67 | contentContainerStyle={{ columnGap: SIZES.xSmall }}
68 | horizontal
69 | showsHorizontalScrollIndicator={false}
70 | />
71 |
72 |
73 | );
74 | }
75 |
76 | const styles = StyleSheet.create({
77 | searchContainer: {
78 | justifyContent: "center",
79 | alignItems: "center",
80 | flexDirection: "row",
81 | marginTop: SIZES.xLarge,
82 | height: 50,
83 | },
84 | searchWrapper: {
85 | flex: 1,
86 | backgroundColor: COLORS.lightWhite,
87 | justifyContent: "center",
88 | alignItems: "center",
89 | height: "100%",
90 | },
91 | searchInput: {
92 | width: "100%",
93 | height: "100%",
94 | paddingHorizontal: SIZES.medium,
95 | fontFamily: FONTS.medium,
96 | },
97 | searchBtn: {
98 | width: 50,
99 | height: "100%",
100 | backgroundColor: COLORS.tertiary,
101 | justifyContent: "center",
102 | alignItems: "center",
103 | },
104 | searchBtnIcon: {
105 | width: "50%",
106 | height: "50%",
107 | tintColor: COLORS.white,
108 | },
109 | filterContainer: {
110 | alignItems: "center",
111 | marginTop: SIZES.large,
112 | },
113 | filter: (activeFilterJob, item) => ({
114 | paddingVertical: 6,
115 | paddingHorizontal: SIZES.small,
116 | borderWidth: 1,
117 | borderColor:
118 | activeFilterJob === item ? COLORS.secondary : COLORS.lightWhite,
119 | backgroundColor:
120 | activeFilterJob === item ? COLORS.secondary : COLORS.lightWhite,
121 | }),
122 | filterTitle: (activeFilterJob, item) => ({
123 | color: activeFilterJob === item ? COLORS.white : COLORS.gray,
124 | fontFamily: FONTS.bold,
125 | }),
126 | });
127 |
--------------------------------------------------------------------------------
/components/cards/my-job-card.js:
--------------------------------------------------------------------------------
1 | import { View, Text, StyleSheet, Image } from "react-native";
2 | import React, { useState } from "react";
3 | import { TouchableOpacity } from "react-native-gesture-handler";
4 | import { COLORS, FONTS, SIZES, icons, SHADOWS } from "../../constants";
5 | import { useRouter } from "expo-router";
6 |
7 | export default function MyJobCard({ item, selectedJob, setSelectedJob }) {
8 | const router = useRouter();
9 |
10 | const handlePress = () => {
11 | router.push(`/details/${item?.job_id}`);
12 | setSelectedJob(item?.job_id);
13 | };
14 |
15 | return (
16 |
20 |
21 |
22 |
30 |
31 | {item?.employer_name.length >= 20
32 | ? item?.employer_name.slice(0, 20) + "..."
33 | : item?.employer_name}
34 |
35 |
36 |
37 |
41 |
42 |
43 |
44 |
45 |
46 | {item?.job_title}
47 |
48 |
49 |
50 |
51 | Employer type -{" "}
52 |
53 | {item?.job_employment_type}
54 |
55 |
56 |
57 | {item?.job_publisher} -{" "}
58 |
59 | {item?.job_country}
60 |
61 |
62 |
63 | );
64 | }
65 |
66 | const styles = StyleSheet.create({
67 | container: (selectedJob, job_id) => ({
68 | width: "100%",
69 | marginTop: SIZES.medium,
70 | padding: SIZES.xLarge,
71 | backgroundColor: selectedJob === job_id ? COLORS.secondary : COLORS.white,
72 | borderRadius: SIZES.medium,
73 | ...SHADOWS.medium,
74 | shadowColor: COLORS.white,
75 | }),
76 | employerLogo: {
77 | width: 50,
78 | height: 50,
79 | },
80 | employerWrapper: {
81 | justifyContent: "space-between",
82 | flexDirection: "row",
83 | },
84 | heartIcon: (selectedJob, job_id) => ({
85 | width: 20,
86 | height: 20,
87 | tintColor: selectedJob === job_id ? COLORS.white : COLORS.primary,
88 | }),
89 | employerHeader: {
90 | flexDirection: "row",
91 | alignItems: "center",
92 | gap: SIZES.xSmall,
93 | },
94 | employerTitle: {
95 | fontFamily: FONTS.medium,
96 | fontSize: SIZES.medium,
97 | color: COLORS.gray,
98 | },
99 | infoContainer: {
100 | marginTop: SIZES.medium,
101 | },
102 | jobName: (selectedJob, job_id) => ({
103 | fontSize: SIZES.large,
104 | fontFamily: FONTS.bold,
105 | color: selectedJob === job_id ? COLORS.white : COLORS.secondary,
106 | }),
107 | infoWrapper: {
108 | flexDirection: "row",
109 | marginTop: SIZES.small / 2,
110 | justifyContent: "flex-start",
111 | alignItems: "center",
112 | },
113 | info1: (selectedJob, job_id) => ({
114 | fontSize: SIZES.medium,
115 | fontFamily: FONTS.medium,
116 | color: selectedJob === job_id ? COLORS.gray2 : COLORS.primary,
117 | }),
118 | info2: {
119 | fontSize: SIZES.medium,
120 | fontFamily: FONTS.regular,
121 | color: COLORS.gray,
122 | },
123 | });
124 |
--------------------------------------------------------------------------------
/app/search/[id].js:
--------------------------------------------------------------------------------
1 | import {
2 | View,
3 | Text,
4 | SafeAreaView,
5 | FlatList,
6 | StyleSheet,
7 | ActivityIndicator,
8 | TouchableOpacity,
9 | Image,
10 | } from "react-native";
11 | import React, { useEffect, useState } from "react";
12 | import { COLORS, FONTS, SIZES, icons } from "../../constants";
13 | import { Stack, useGlobalSearchParams, useRouter } from "expo-router";
14 | import { HeaderBtn } from "../../components";
15 | import useRequest from "../../hook/useRequest";
16 | import JobCard from "../../components/cards/job-card";
17 | import axios from "axios";
18 |
19 | export default function Search() {
20 | const router = useRouter();
21 | const params = useGlobalSearchParams();
22 |
23 | const [data, setData] = useState([]);
24 | const [isLoading, setIsLoading] = useState(false);
25 | const [error, setError] = useState(null);
26 | const [page, setpage] = useState(1);
27 |
28 | const handleSearch = async () => {
29 | setIsLoading(true);
30 | setData([]);
31 | try {
32 | const option = {
33 | method: "GET",
34 | url: `https://jsearch.p.rapidapi.com/search`,
35 | headers: {
36 | "X-RapidAPI-Key":
37 | "8e99652f73msh0d6b44b17e30ae3p165001jsna1eed97a1b9c",
38 | "X-RapidAPI-Host": "jsearch.p.rapidapi.com",
39 | },
40 | params: {
41 | query: params.id,
42 | page: page.toString(),
43 | },
44 | };
45 |
46 | const { data: res } = await axios.request(option);
47 | setData(res.data);
48 | } catch (error) {
49 | setError(error);
50 | console.log(error);
51 | } finally {
52 | setIsLoading(false);
53 | }
54 | };
55 |
56 | const handlePagination = (direction) => {
57 | if (direction === "left" && page > 1) {
58 | setpage(page - 1);
59 | handleSearch();
60 | } else if (direction === "right") {
61 | setpage(page + 1);
62 | handleSearch();
63 | }
64 | };
65 |
66 | useEffect(() => {
67 | handleSearch();
68 | }, []);
69 |
70 | return (
71 |
72 | (
78 | router.back()}
82 | />
83 | ),
84 | }}
85 | />
86 |
87 | (
90 | router.push(`/details/${item.job_id}`)}
93 | />
94 | )}
95 | keyExtractor={(item) => `search-job-${item.job_id}`}
96 | contentContainerStyle={{ padding: SIZES.medium, rowGap: SIZES.medium }}
97 | ListHeaderComponent={() => (
98 | <>
99 |
100 | {params.id}
101 | Job Opportunities
102 |
103 |
104 | {isLoading ? (
105 |
106 | ) : (
107 | error && Something went wrong
108 | )}
109 |
110 | >
111 | )}
112 | ListFooterComponent={() => (
113 |
114 | handlePagination("left")}
117 | >
118 |
123 |
124 |
125 | {page}
126 |
127 | handlePagination("right")}
130 | >
131 |
136 |
137 |
138 | )}
139 | />
140 |
141 | );
142 | }
143 |
144 | const styles = StyleSheet.create({
145 | container: {
146 | width: "100%",
147 | },
148 | searchTitle: {
149 | fontFamily: FONTS.bold,
150 | fontSize: SIZES.xLarge,
151 | color: COLORS.primary,
152 | },
153 | noOfSearchedJobs: {
154 | marginTop: 2,
155 | fontFamily: FONTS.medium,
156 | fontSize: SIZES.small,
157 | color: COLORS.primary,
158 | },
159 | loaderWrapper: {
160 | marginTop: SIZES.medium,
161 | },
162 | footerContainer: {
163 | marginTop: SIZES.small,
164 | justifyContent: "center",
165 | alignItems: "center",
166 | flexDirection: "row",
167 | gap: 10,
168 | },
169 | paginationBtn: {
170 | width: 30,
171 | height: 30,
172 | borderRadius: 5,
173 | justifyContent: "center",
174 | alignItems: "center",
175 | backgroundColor: COLORS.tertiary,
176 | },
177 | paginationIcon: {
178 | width: "60%",
179 | height: "60%",
180 | tintColor: COLORS.white,
181 | },
182 | paginationTextWrapper: {
183 | width: 30,
184 | height: 30,
185 | borderRadius: 2,
186 | justifyContent: "center",
187 | alignItems: "center",
188 | backgroundColor: COLORS.white,
189 | },
190 | paginationText: {
191 | fontFamily: FONTS.bold,
192 | fontSize: SIZES.medium,
193 | color: COLORS.primary,
194 | },
195 | });
196 |
--------------------------------------------------------------------------------