@recruitnepal/shared-packages 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
Binary file
@@ -0,0 +1,12 @@
1
+ export declare function initApi(config: {
2
+ baseURL: string;
3
+ }): void;
4
+ export declare function getBaseURL(): string;
5
+ export declare const API: {
6
+ vacancies: {
7
+ list: () => string;
8
+ byId: (id: string) => string;
9
+ intern: () => string;
10
+ };
11
+ };
12
+ //# sourceMappingURL=api-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAEA,wBAAgB,OAAO,CAAC,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,QAElD;AAED,wBAAgB,UAAU,WAKzB;AAED,eAAO,MAAM,GAAG;;;mBAGD,MAAM;;;CAGpB,CAAC"}
@@ -0,0 +1,17 @@
1
+ let _baseURL = '';
2
+ export function initApi(config) {
3
+ _baseURL = config.baseURL.replace(/\/$/, '');
4
+ }
5
+ export function getBaseURL() {
6
+ if (!_baseURL) {
7
+ throw new Error('initApi({ baseURL }) must be called first');
8
+ }
9
+ return _baseURL;
10
+ }
11
+ export const API = {
12
+ vacancies: {
13
+ list: () => `${getBaseURL()}/vacancies`,
14
+ byId: (id) => `${getBaseURL()}/vacancies/${id}`,
15
+ intern: () => `${getBaseURL()}/intern/vacancies`,
16
+ },
17
+ };
@@ -0,0 +1,28 @@
1
+ import type { SingleVacancyRes } from '../types';
2
+ type Filter = {
3
+ keyword: string;
4
+ location: string;
5
+ category: string[];
6
+ };
7
+ export type JobsClientProps = {
8
+ initialVacancies: SingleVacancyRes[];
9
+ initialQuery: string;
10
+ initialLocation: string;
11
+ initialCategory: string;
12
+ totalJobs: number;
13
+ /** Optional header component */
14
+ headerComponent?: React.ComponentType<{
15
+ jobCount: number;
16
+ handleSearch: (filter: Filter) => void;
17
+ }>;
18
+ /** Optional job listing component */
19
+ jobListingComponent?: React.ComponentType<{
20
+ joblist: SingleVacancyRes[];
21
+ jobCount: number;
22
+ }>;
23
+ /** Optional className for the container */
24
+ className?: string;
25
+ };
26
+ export default function JobsClient({ initialVacancies, initialQuery, initialLocation, initialCategory, totalJobs, headerComponent: HeaderComponent, jobListingComponent: JobListingComponent, className, }: JobsClientProps): import("react/jsx-runtime").JSX.Element;
27
+ export {};
28
+ //# sourceMappingURL=JobsClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JobsClient.d.ts","sourceRoot":"","sources":["../../src/components/JobsClient.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAEjD,KAAK,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAExE,MAAM,MAAM,eAAe,GAAG;IAC5B,gBAAgB,EAAE,gBAAgB,EAAE,CAAC;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,gCAAgC;IAChC,eAAe,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;QACpC,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;KACxC,CAAC,CAAC;IACH,qCAAqC;IACrC,mBAAmB,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;QACxC,OAAO,EAAE,gBAAgB,EAAE,CAAC;QAC5B,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;IACH,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,EACjC,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,eAAe,EACf,SAAS,EACT,eAAe,EAAE,eAAe,EAChC,mBAAmB,EAAE,mBAAmB,EACxC,SAAS,GACV,EAAE,eAAe,2CA0FjB"}
@@ -0,0 +1,50 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useState } from 'react';
4
+ import { useRouter, useSearchParams } from 'next/navigation';
5
+ export default function JobsClient({ initialVacancies, initialQuery, initialLocation, initialCategory, totalJobs, headerComponent: HeaderComponent, jobListingComponent: JobListingComponent, className, }) {
6
+ const router = useRouter();
7
+ const searchParams = useSearchParams();
8
+ const [_filter, setFilter] = useState({
9
+ keyword: initialQuery,
10
+ location: initialLocation,
11
+ category: initialCategory ? initialCategory.split(',') : [],
12
+ });
13
+ const handleSearch = (next) => {
14
+ setFilter(next);
15
+ const url = new URL(window.location.href);
16
+ const kw = next.keyword.trim();
17
+ if (kw) {
18
+ url.searchParams.set('query', kw);
19
+ }
20
+ else {
21
+ url.searchParams.delete('query');
22
+ }
23
+ const loc = next.location.trim();
24
+ if (loc) {
25
+ url.searchParams.set('location', loc);
26
+ }
27
+ else {
28
+ url.searchParams.delete('location');
29
+ }
30
+ if (next.category.length > 0) {
31
+ url.searchParams.set('category', next.category.join(','));
32
+ }
33
+ else {
34
+ url.searchParams.delete('category');
35
+ }
36
+ // Use push to trigger server-side re-render with new params
37
+ router.push(url.toString());
38
+ };
39
+ // Sync filter state with URL params when they change (e.g., back/forward navigation)
40
+ useEffect(() => {
41
+ setFilter({
42
+ keyword: searchParams?.get('query') || '',
43
+ location: searchParams?.get('location') || '',
44
+ category: searchParams?.get('category')
45
+ ? searchParams.get('category').split(',')
46
+ : [],
47
+ });
48
+ }, [searchParams]);
49
+ return (_jsxs("div", { className: className, children: [HeaderComponent ? (_jsx(HeaderComponent, { jobCount: totalJobs, handleSearch: handleSearch })) : (_jsxs("div", { className: "p-4 border-b", children: [_jsxs("p", { className: "text-sm text-gray-600", children: [totalJobs, " job", totalJobs !== 1 ? 's' : '', " found"] }), _jsx("p", { className: "text-xs text-gray-500 mt-1", children: "Provide a headerComponent prop to customize the header" })] })), _jsx("div", { className: "max-w-7xl px-6 mx-auto mt-12", children: JobListingComponent ? (_jsx(JobListingComponent, { joblist: initialVacancies, jobCount: totalJobs })) : (_jsx("div", { className: "space-y-4", children: initialVacancies.length === 0 ? (_jsx("p", { className: "text-center text-gray-500 py-8", children: "No jobs found" })) : (initialVacancies.map((job) => (_jsxs("div", { className: "p-4 border rounded-lg hover:shadow-md transition-shadow", children: [_jsx("h3", { className: "font-semibold text-lg", children: job.title }), _jsx("p", { className: "text-sm text-gray-600 mt-1", children: job.company_name }), _jsx("p", { className: "text-xs text-gray-500 mt-1", children: job.location }), _jsx("p", { className: "text-xs text-gray-500 mt-1", children: "Provide a jobListingComponent prop to customize the listing" })] }, job.id)))) })) })] }));
50
+ }
@@ -0,0 +1,43 @@
1
+ import type { SingleVacancyRes } from '../types';
2
+ type Mode = 'all' | 'paged';
3
+ type Config = {
4
+ publicFetch?: boolean;
5
+ mode?: Mode;
6
+ page?: number;
7
+ limit?: number;
8
+ companyId?: string;
9
+ forceStatus?: string;
10
+ /** When true, fetches from /intern/vacancies (intern vacancies list) */
11
+ isIntern?: boolean;
12
+ /** Optional token for authenticated requests */
13
+ token?: string;
14
+ /** Optional search params - if not provided, will try to use next/navigation useSearchParams */
15
+ searchParams?: URLSearchParams | null;
16
+ };
17
+ type UnifiedPagination = {
18
+ total: number;
19
+ pages: number;
20
+ page: number;
21
+ nextPage: number | null;
22
+ prevPage: number | null;
23
+ };
24
+ export declare const useGetAllVacancy: ({ publicFetch, mode, page, limit, companyId, forceStatus, isIntern, token, searchParams: providedSearchParams, }?: Config) => {
25
+ mode: Mode;
26
+ vacancies: SingleVacancyRes[];
27
+ total: number;
28
+ pages: number;
29
+ pagination: UnifiedPagination;
30
+ isLoading: boolean;
31
+ isError: boolean;
32
+ error: Error | null;
33
+ counts: {
34
+ active?: number;
35
+ pending?: number;
36
+ expired?: number;
37
+ drafted?: number;
38
+ denied?: number;
39
+ all?: number;
40
+ } | undefined;
41
+ };
42
+ export default useGetAllVacancy;
43
+ //# sourceMappingURL=useGetAllVacancy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useGetAllVacancy.d.ts","sourceRoot":"","sources":["../../src/hooks/useGetAllVacancy.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAiB,gBAAgB,EAAc,MAAM,UAAU,CAAC;AAO5E,KAAK,IAAI,GAAG,KAAK,GAAG,OAAO,CAAC;AAE5B,KAAK,MAAM,GAAG;IACZ,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gGAAgG;IAChG,YAAY,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;CACvC,CAAC;AAEF,KAAK,iBAAiB,GAAG;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,CAAC;AAkBF,eAAO,MAAM,gBAAgB,GAAI,mHAU9B,MAAW;;;;;;;;;;iBAnBD,MAAM;kBACL,MAAM;kBACN,MAAM;kBACN,MAAM;iBACP,MAAM;cACT,MAAM;;CAgOf,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
@@ -0,0 +1,178 @@
1
+ 'use client';
2
+ import { useQuery } from '@tanstack/react-query';
3
+ import { apiRequest } from '../utils/api-request';
4
+ // Import useSearchParams from next/navigation
5
+ // Next.js is a peer dependency, so this should be available
6
+ // If not using Next.js, provide searchParams in the config
7
+ import { useSearchParams as useNextSearchParams } from 'next/navigation';
8
+ export const useGetAllVacancy = ({ publicFetch = true, mode = 'all', page, limit, companyId, forceStatus, isIntern = false, token, searchParams: providedSearchParams, } = {}) => {
9
+ const isReady = !!token || publicFetch;
10
+ // Use Next.js hook if no searchParams provided
11
+ // If not using Next.js, provide searchParams in config
12
+ const nextSearchParams = !providedSearchParams ? useNextSearchParams() : null;
13
+ const searchParams = providedSearchParams ?? nextSearchParams ?? new URLSearchParams();
14
+ const searchQuery = searchParams?.get('query') || '';
15
+ const urlLimit = Number(searchParams?.get('pageLimit') || '50');
16
+ const location = searchParams?.get('location') || '';
17
+ const category = searchParams?.get('category') || '';
18
+ // ✅ company filter: prefer prop, fallback to URL
19
+ const companyIdParam = companyId ?? searchParams?.get('companyId') ?? '';
20
+ // 👇 Use forceStatus if provided, otherwise use URL param
21
+ const statusParam = forceStatus ?? searchParams?.get('status') ?? '';
22
+ const status = statusParam === 'all' ? '' : statusParam;
23
+ const sortBy = searchParams?.get('sort') || '';
24
+ const buildUnified = (pg, fallbackTotal = 0) => {
25
+ const p = pg?.page ?? 1;
26
+ const ps = pg?.pages ?? 1;
27
+ const nx = pg?.nextPage ?? (p < ps ? p + 1 : null);
28
+ const pv = p > 1 ? p - 1 : null;
29
+ return {
30
+ total: pg?.total ?? fallbackTotal,
31
+ pages: ps,
32
+ page: p,
33
+ nextPage: nx,
34
+ prevPage: pv,
35
+ };
36
+ };
37
+ // Helper to build clean query string
38
+ const buildQueryString = (pageNum, pageLimit) => {
39
+ const params = new URLSearchParams();
40
+ params.set('page', String(pageNum));
41
+ params.set('limit', pageLimit);
42
+ if (searchQuery)
43
+ params.set('query', searchQuery);
44
+ if (location)
45
+ params.set('location', location);
46
+ if (category)
47
+ params.set('category', category);
48
+ if (companyIdParam)
49
+ params.set('companyId', companyIdParam);
50
+ if (status)
51
+ params.set('status', status);
52
+ if (sortBy) {
53
+ params.set('sortBy', 'created_at');
54
+ params.set('sortOrder', sortBy);
55
+ }
56
+ return params.toString();
57
+ };
58
+ const listBasePath = isIntern ? '/intern/vacancies' : '/vacancies';
59
+ const fetchPaged = async () => {
60
+ const p = page ?? 1;
61
+ const l = String(limit ?? urlLimit);
62
+ const queryString = buildQueryString(p, l);
63
+ const res = await apiRequest({
64
+ endpoint: `${listBasePath}?${queryString}&_t=${Date.now()}`,
65
+ method: 'GET',
66
+ token: publicFetch ? undefined : token,
67
+ });
68
+ if (!res.success || !res.data) {
69
+ throw new Error('Failed to fetch vacancies');
70
+ }
71
+ const payload = res.data;
72
+ const rows = payload?.data?.data ?? [];
73
+ const sp = payload?.data?.pagination;
74
+ const pagination = buildUnified({
75
+ total: sp?.total,
76
+ pages: sp?.pages,
77
+ page: sp?.page,
78
+ nextPage: sp?.nextPage ?? null,
79
+ }, rows.length);
80
+ return {
81
+ mode: 'paged',
82
+ vacancies: rows,
83
+ total: pagination.total,
84
+ pages: pagination.pages,
85
+ pagination,
86
+ counts: {
87
+ active: payload?.data.activeVacancies,
88
+ all: payload?.data.allVacancies,
89
+ drafted: payload?.data.draftedVacancies,
90
+ denied: payload?.data.deniedVacancies,
91
+ pending: payload?.data.pendingVacancies,
92
+ expired: payload?.data.expiredVacancies,
93
+ },
94
+ };
95
+ };
96
+ const fetchAll = async () => {
97
+ const l = String(limit ?? urlLimit);
98
+ const pagesArr = [];
99
+ let next = 1;
100
+ while (next) {
101
+ const queryString = buildQueryString(next, l);
102
+ const res = await apiRequest({
103
+ endpoint: `${listBasePath}?${queryString}`,
104
+ method: 'GET',
105
+ token: publicFetch ? undefined : token,
106
+ });
107
+ if (!res.success || !res.data) {
108
+ throw new Error('Failed to fetch vacancies');
109
+ }
110
+ const payload = res.data;
111
+ if (payload)
112
+ pagesArr.push(payload);
113
+ const np = payload?.data?.pagination?.nextPage;
114
+ next = np && Number.isFinite(np) ? Number(np) : undefined;
115
+ }
116
+ const all = pagesArr.flatMap((p) => p?.data?.data ?? []);
117
+ const pg = pagesArr.at(-1)?.data?.pagination ?? pagesArr[0]?.data?.pagination;
118
+ const pagination = buildUnified({
119
+ total: pg?.total,
120
+ pages: pg?.pages,
121
+ page: 1,
122
+ nextPage: null,
123
+ }, all.length);
124
+ return {
125
+ mode: 'all',
126
+ vacancies: all,
127
+ total: pagination.total,
128
+ pages: pagination.pages,
129
+ pagination,
130
+ };
131
+ };
132
+ // Use the actual limit value that will be used in the fetch
133
+ const effectiveLimit = limit ?? urlLimit;
134
+ const rq = useQuery({
135
+ queryKey: [
136
+ isIntern ? 'intern_vacancies' : 'vacancies',
137
+ mode,
138
+ page ?? null,
139
+ effectiveLimit,
140
+ searchQuery,
141
+ location,
142
+ category,
143
+ companyIdParam,
144
+ statusParam,
145
+ sortBy,
146
+ publicFetch ? 'pub' : 'auth',
147
+ token ?? 'no-token',
148
+ isIntern,
149
+ ],
150
+ queryFn: mode === 'paged' ? fetchPaged : fetchAll,
151
+ enabled: isReady,
152
+ retry: false,
153
+ placeholderData: (prev) => prev,
154
+ staleTime: 0,
155
+ refetchOnMount: true,
156
+ refetchOnWindowFocus: false,
157
+ });
158
+ const qd = rq.data;
159
+ return {
160
+ mode: qd?.mode ?? mode,
161
+ vacancies: qd?.vacancies ?? [],
162
+ total: qd?.total ?? 0,
163
+ pages: qd?.pages ?? 0,
164
+ pagination: qd?.pagination ??
165
+ {
166
+ total: 0,
167
+ pages: 1,
168
+ page: 1,
169
+ nextPage: null,
170
+ prevPage: null,
171
+ },
172
+ isLoading: rq.isLoading,
173
+ isError: rq.isError,
174
+ error: rq.error,
175
+ counts: qd?.counts,
176
+ };
177
+ };
178
+ export default useGetAllVacancy;
@@ -0,0 +1,9 @@
1
+ export { initApi, getBaseURL, API } from './api-client';
2
+ export type { SingleVacancyRes, VacancyApiRes, Pagination, ExperienceLevel, } from './types';
3
+ export { apiRequest } from './utils/api-request';
4
+ export type { ApiResponse } from './utils/api-request';
5
+ export { useGetAllVacancy } from './hooks/useGetAllVacancy';
6
+ export { default as useGetAllVacancyDefault } from './hooks/useGetAllVacancy';
7
+ export { default as JobsClient } from './components/JobsClient';
8
+ export type { JobsClientProps } from './components/JobsClient';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAGxD,YAAY,EACV,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,eAAe,GAChB,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,YAAY,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAG9E,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAChE,YAAY,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ // API Client
2
+ export { initApi, getBaseURL, API } from './api-client';
3
+ // Utils
4
+ export { apiRequest } from './utils/api-request';
5
+ // Hooks
6
+ export { useGetAllVacancy } from './hooks/useGetAllVacancy';
7
+ export { default as useGetAllVacancyDefault } from './hooks/useGetAllVacancy';
8
+ // Components
9
+ export { default as JobsClient } from './components/JobsClient';
@@ -0,0 +1,65 @@
1
+ export type ExperienceLevel = {
2
+ level: string;
3
+ };
4
+ export type SingleVacancyRes = {
5
+ id: string;
6
+ slug: string;
7
+ title: string;
8
+ description: string;
9
+ key_responsibilities: string;
10
+ education_level: string;
11
+ employment_type: string[];
12
+ work_approach: string[];
13
+ experience_required: number | null;
14
+ gender_preferred: string;
15
+ liscence_required: boolean;
16
+ vehicle_required: boolean;
17
+ location: string;
18
+ openings: string;
19
+ salary_type: string;
20
+ offered_salary: {
21
+ min: number;
22
+ max: number;
23
+ } | null;
24
+ skills_required: string[];
25
+ job_tags: string[];
26
+ experience_level: ExperienceLevel;
27
+ category: string;
28
+ company_id: string;
29
+ company_slug: string;
30
+ company_name: string;
31
+ organization_name: string;
32
+ company_size: string;
33
+ about_company: string;
34
+ company_logo: string | null;
35
+ hide_company_name: boolean;
36
+ is_registered: boolean;
37
+ status: string;
38
+ total_views: number | string;
39
+ deadline_date: string;
40
+ created_at: string;
41
+ rank: string;
42
+ arrange_vacancy_type: string;
43
+ total_applicants: number;
44
+ total_rejected: number;
45
+ isFulfilled?: boolean;
46
+ };
47
+ export type Pagination = {
48
+ total: number;
49
+ page: number;
50
+ pages: number;
51
+ nextPage: number | null;
52
+ };
53
+ export type VacancyApiRes = {
54
+ data: {
55
+ allVacancies: number;
56
+ activeVacancies: number;
57
+ draftedVacancies: number;
58
+ expiredVacancies: number;
59
+ deniedVacancies: number;
60
+ pendingVacancies: number;
61
+ data: SingleVacancyRes[];
62
+ pagination: Pagination;
63
+ };
64
+ };
65
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IAGb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,oBAAoB,EAAE,MAAM,CAAC;IAG7B,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,gBAAgB,EAAE,OAAO,CAAC;IAG1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IAGpB,cAAc,EAAE;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;KACb,GAAG,IAAI,CAAC;IAET,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,QAAQ,EAAE,MAAM,EAAE,CAAC;IAGnB,gBAAgB,EAAE,eAAe,CAAC;IAGlC,QAAQ,EAAE,MAAM,CAAC;IAGjB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,iBAAiB,EAAE,OAAO,CAAC;IAE3B,aAAa,EAAE,OAAO,CAAC;IAGvB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IAEnB,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,EAAE,MAAM,CAAC;IAG7B,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IAGvB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE;QACJ,YAAY,EAAE,MAAM,CAAC;QACrB,eAAe,EAAE,MAAM,CAAC;QACxB,gBAAgB,EAAE,MAAM,CAAC;QACzB,gBAAgB,EAAE,MAAM,CAAC;QACzB,eAAe,EAAE,MAAM,CAAC;QACxB,gBAAgB,EAAE,MAAM,CAAC;QACzB,IAAI,EAAE,gBAAgB,EAAE,CAAC;QACzB,UAAU,EAAE,UAAU,CAAC;KACxB,CAAC;CACH,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
2
+ export interface ApiResponse<T> {
3
+ success: boolean;
4
+ data?: T;
5
+ errors?: Record<string, string[]>;
6
+ }
7
+ interface ApiRequestParams {
8
+ endpoint: string;
9
+ method?: HttpMethod;
10
+ data?: any;
11
+ token?: string;
12
+ multipart?: boolean;
13
+ }
14
+ export declare const apiRequest: <T>({ endpoint, method, data, token, multipart, }: ApiRequestParams) => Promise<ApiResponse<T>>;
15
+ export {};
16
+ //# sourceMappingURL=api-request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-request.d.ts","sourceRoot":"","sources":["../../src/utils/api-request.ts"],"names":[],"mappings":"AAGA,KAAK,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE9D,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACnC;AAED,UAAU,gBAAgB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,eAAO,MAAM,UAAU,GAAU,CAAC,EAAE,+CAMjC,gBAAgB,KAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CA8D3C,CAAC"}
@@ -0,0 +1,60 @@
1
+ import axios from 'axios';
2
+ import { getBaseURL } from '../api-client';
3
+ export const apiRequest = async ({ endpoint, method = 'GET', data, token, multipart = false, }) => {
4
+ const baseURL = getBaseURL();
5
+ const url = endpoint.startsWith('http') ? endpoint : `${baseURL}${endpoint}`;
6
+ const config = {
7
+ method,
8
+ url,
9
+ headers: {
10
+ ...(multipart
11
+ ? { 'Content-Type': 'multipart/form-data' }
12
+ : { 'Content-Type': 'application/json' }),
13
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
14
+ },
15
+ data,
16
+ validateStatus: (status) => status < 500, // Don't throw for 4xx errors
17
+ };
18
+ try {
19
+ const response = await axios(config);
20
+ // Successful response (2xx)
21
+ if (response.status >= 200 && response.status < 300) {
22
+ return {
23
+ success: true,
24
+ data: response.data,
25
+ };
26
+ }
27
+ // Handle 4xx responses as business errors
28
+ return {
29
+ success: false,
30
+ errors: {
31
+ general: [
32
+ response.data?.message ||
33
+ `Request failed with status ${response.status}`,
34
+ ],
35
+ },
36
+ };
37
+ }
38
+ catch (error) {
39
+ if (axios.isAxiosError(error)) {
40
+ const axiosError = error;
41
+ if (axiosError.response?.data) {
42
+ const errorData = axiosError.response.data;
43
+ return {
44
+ success: false,
45
+ errors: {
46
+ general: Array.isArray(errorData.message)
47
+ ? errorData.message
48
+ : [String(errorData.message || 'An error occurred')],
49
+ },
50
+ };
51
+ }
52
+ }
53
+ return {
54
+ success: false,
55
+ errors: {
56
+ general: ['Network error or unexpected failure'],
57
+ },
58
+ };
59
+ }
60
+ };
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@recruitnepal/shared-packages",
3
+ "version": "1.0.0",
4
+ "description": "Shared UI components and hooks for Recruit Nepal projects",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "dev": "tsc --watch",
10
+ "prepublishOnly": "npm run build"
11
+ },
12
+ "keywords": [
13
+ "react",
14
+ "nextjs",
15
+ "recruit-nepal",
16
+ "shared-components"
17
+ ],
18
+ "author": "",
19
+ "license": "MIT",
20
+ "peerDependencies": {
21
+ "react": "^18.2.0",
22
+ "react-dom": "^18.2.0",
23
+ "next": "^14.0.0",
24
+ "@tanstack/react-query": "^5.50.0"
25
+ },
26
+ "dependencies": {
27
+ "axios": "^1.7.8"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^20.11.17",
31
+ "@types/react": "^18.2.55",
32
+ "@types/react-dom": "^18.2.19",
33
+ "typescript": "^5.3.3"
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "README.md"
38
+ ]
39
+ }