···11// Re-export the main component from the organized structure
22-export { ServerStatusIndicator } from "./ServerStatusIndicator/index";22+export { ServerStatusIndicator } from "./ServerStatusIndicator/index";
···11export { default as CloseIcon } from "./CloseIcon";
22-export { default as ToastIcon } from "./ToastIcon";
33-export { default as EditIcon } from "./EditIcon";
42export { default as DeleteIcon } from "./DeleteIcon";
33+export { default as DocumentIcon } from "./DocumentIcon";
44+export { default as EditIcon } from "./EditIcon";
55export { default as ErrorIcon } from "./ErrorIcon";
66+export { default as LinkIcon } from "./LinkIcon";
67export { default as LoadingIcon } from "./LoadingIcon";
88+export { default as ToastIcon } from "./ToastIcon";
99+export { default as UploadIcon } from "./UploadIcon";
+12
apps/client/src/components/navLinks.ts
···11+export type NavLink = {
22+ to: string;
33+ label: string;
44+};
55+66+export const defaultNavLinks: NavLink[] = [
77+ { to: "/", label: "Dashboard" },
88+ { to: "/job-experience", label: "Job Experience" },
99+ { to: "/organizations", label: "Organizations" },
1010+ { to: "/vacancies", label: "Vacancies" },
1111+ { to: "/profile", label: "Profile" },
1212+];
···11-query Me {
22- me {
33- id
44- email
55- name
66- createdAt
77- organizations {
88- id
99- name
1010- description
1111- createdAt
1212- updatedAt
1313- users {
1414- id
1515- joinedAt
1616- user {
1717- id
1818- name
1919- email
2020- createdAt
2121- experience {
2222- id
2323- startDate
2424- endDate
2525- description
2626- company {
2727- id
2828- name
2929- website
3030- }
3131- role {
3232- id
3333- name
3434- }
3535- level {
3636- id
3737- name
3838- }
3939- skills {
4040- id
4141- name
4242- }
4343- }
4444- }
4545- }
4646- }
4747- }
4848-}
+29-18
apps/client/src/hooks/useServerHealth.ts
···11-import { useRef, useEffect } from "react";
22-import { useHealthQuery } from "@/generated/graphql";
11+import { useEffect, useRef } from "react";
32import { useToast } from "@/contexts/ToastContext";
33+import { useHealthQuery } from "@/generated/graphql";
4455type ServerHealthStatus = "checking" | "online" | "offline";
66···1313 const initialCheckRef = useRef(false);
1414 const lastErrorToastRef = useRef<Date | null>(null);
1515 const wasOfflineRef = useRef(false);
1616-1616+1717 // Use Apollo's generated hook with polling
1818 const healthQuery = useHealthQuery({
1919 fetchPolicy: "network-only",
···2626 useEffect(() => {
2727 const { data, error } = healthQuery;
2828 const now = new Date();
2929-2929+3030 if (error && !data) {
3131 // Only show error toast if:
3232 // 1. It's not the initial check (to avoid showing error immediately on app load)
3333 // 2. We haven't shown an error toast in the last 60 seconds (to avoid spam)
3434- const shouldShowError = initialCheckRef.current &&
3535- (!lastErrorToastRef.current ||
3636- (now.getTime() - lastErrorToastRef.current.getTime()) > 60000);
3737-3434+ const shouldShowError =
3535+ initialCheckRef.current &&
3636+ (!lastErrorToastRef.current ||
3737+ now.getTime() - lastErrorToastRef.current.getTime() > 60000);
3838+3839 if (shouldShowError) {
3940 lastErrorToastRef.current = now;
4040- const serverUrl = import.meta.env["VITE_SERVER_URL"] || "http://localhost:3000";
4141- showError("Server Unreachable", `Cannot connect to ${serverUrl}. Check your connection and try again.`);
4141+ const serverUrl =
4242+ import.meta.env["VITE_SERVER_URL"] || "http://localhost:3000";
4343+ showError(
4444+ "Server Unreachable",
4545+ `Cannot connect to ${serverUrl}. Check your connection and try again.`,
4646+ );
4247 }
4343-4848+4449 wasOfflineRef.current = true;
4550 } else if (data) {
4651 // Show success toast only when recovering from offline state
4752 if (wasOfflineRef.current) {
4848- const serverUrl = import.meta.env["VITE_SERVER_URL"] || "http://localhost:3000";
4949- showSuccess("Server Connected", `Successfully connected to ${serverUrl}`);
5353+ const serverUrl =
5454+ import.meta.env["VITE_SERVER_URL"] || "http://localhost:3000";
5555+ showSuccess(
5656+ "Server Connected",
5757+ `Successfully connected to ${serverUrl}`,
5858+ );
5059 }
5151-6060+5261 wasOfflineRef.current = false;
5362 initialCheckRef.current = true;
5463 }
5564 }, [healthQuery, showError, showSuccess]);
56655766 // Compute derived state from Apollo's data
5858- const status: ServerHealthStatus =
5959- healthQuery.loading && !healthQuery.data ? "checking" :
6060- healthQuery.error && !healthQuery.data ? "offline" :
6161- "online";
6767+ const status: ServerHealthStatus =
6868+ healthQuery.loading && !healthQuery.data
6969+ ? "checking"
7070+ : healthQuery.error && !healthQuery.data
7171+ ? "offline"
7272+ : "online";
62736374 return {
6475 // Derived state for convenience
···11// UI Components
22export { default as Button } from "./Button";
33+export { default as Checkbox } from "./Checkbox";
34export { default as IconButton } from "./IconButton";
44-export { default as TextInput } from "./TextInput";
55-export { default as ConfirmationModal } from "./ConfirmationModal";
66-export { default as Toast } from "./Toast";
77-export { default as ToastContainer } from "./ToastContainer";
55+export { Select } from "./Select";
66+export { StatusBadge } from "./StatusBadge";
87export * from "./Table";
99-1010-// Icons
1111-export * from "./icons";
88+export { default as Textarea } from "./Textarea";
99+export { default as TextInput } from "./TextInput";
+24-3
apps/client/src/utils/dateUtils.ts
···5353 */
5454export function formatLastChecked(lastChecked: Date | null): string {
5555 if (!lastChecked) return "";
5656-5656+5757 const now = new Date();
5858 const diffMs = now.getTime() - lastChecked.getTime();
5959 const diffSeconds = Math.floor(diffMs / 1000);
6060-6161- return diffSeconds < 60 ? `${diffSeconds}s ago` : `${Math.floor(diffSeconds / 60)}m ago`;
6060+6161+ return diffSeconds < 60
6262+ ? `${diffSeconds}s ago`
6363+ : `${Math.floor(diffSeconds / 60)}m ago`;
6464+}
6565+6666+/**
6767+ * Formats a date string to a simple date format
6868+ * @param dateString - ISO date string
6969+ * @returns Formatted date string (e.g., "12/25/2023")
7070+ */
7171+export function formatSimpleDate(dateString: string): string {
7272+ return new Date(dateString).toLocaleDateString();
7373+}
7474+7575+/**
7676+ * Formats a date string for deadline display, handling null/undefined
7777+ * @param deadlineString - ISO date string or null/undefined
7878+ * @returns Formatted date string or "—" if null/undefined
7979+ */
8080+export function formatDeadline(deadlineString?: string | null): string {
8181+ if (!deadlineString) return "—";
8282+ return new Date(deadlineString).toLocaleDateString();
6283}
···11-import { ObjectType, Field } from "@nestjs/graphql";
11+import { Field, ObjectType } from "@nestjs/graphql";
2233@ObjectType()
44export class HealthResponse {
···55 providers: [],
66 exports: [],
77})
88-export class BaseModule {}88+export class BaseModule {}
+1-1
apps/server/src/modules/base/mapper.interface.ts
···66 * Maps a single Prisma entity to a domain entity
77 */
88 toDomain(prismaEntity: TPrismaEntity): TDomainEntity;
99-99+1010 /**
1111 * Maps a single Prisma entity to a domain entity, handling null input
1212 */
···11+/* eslint-disable */
22+var addSorting = (function() {
33+ 'use strict';
44+ var cols,
55+ currentSort = {
66+ index: 0,
77+ desc: false
88+ };
99+1010+ // returns the summary table element
1111+ function getTable() {
1212+ return document.querySelector('.coverage-summary');
1313+ }
1414+ // returns the thead element of the summary table
1515+ function getTableHeader() {
1616+ return getTable().querySelector('thead tr');
1717+ }
1818+ // returns the tbody element of the summary table
1919+ function getTableBody() {
2020+ return getTable().querySelector('tbody');
2121+ }
2222+ // returns the th element for nth column
2323+ function getNthColumn(n) {
2424+ return getTableHeader().querySelectorAll('th')[n];
2525+ }
2626+2727+ function onFilterInput() {
2828+ const searchValue = document.getElementById('fileSearch').value;
2929+ const rows = document.getElementsByTagName('tbody')[0].children;
3030+3131+ // Try to create a RegExp from the searchValue. If it fails (invalid regex),
3232+ // it will be treated as a plain text search
3333+ let searchRegex;
3434+ try {
3535+ searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive
3636+ } catch (error) {
3737+ searchRegex = null;
3838+ }
3939+4040+ for (let i = 0; i < rows.length; i++) {
4141+ const row = rows[i];
4242+ let isMatch = false;
4343+4444+ if (searchRegex) {
4545+ // If a valid regex was created, use it for matching
4646+ isMatch = searchRegex.test(row.textContent);
4747+ } else {
4848+ // Otherwise, fall back to the original plain text search
4949+ isMatch = row.textContent
5050+ .toLowerCase()
5151+ .includes(searchValue.toLowerCase());
5252+ }
5353+5454+ row.style.display = isMatch ? '' : 'none';
5555+ }
5656+ }
5757+5858+ // loads the search box
5959+ function addSearchBox() {
6060+ var template = document.getElementById('filterTemplate');
6161+ var templateClone = template.content.cloneNode(true);
6262+ templateClone.getElementById('fileSearch').oninput = onFilterInput;
6363+ template.parentElement.appendChild(templateClone);
6464+ }
6565+6666+ // loads all columns
6767+ function loadColumns() {
6868+ var colNodes = getTableHeader().querySelectorAll('th'),
6969+ colNode,
7070+ cols = [],
7171+ col,
7272+ i;
7373+7474+ for (i = 0; i < colNodes.length; i += 1) {
7575+ colNode = colNodes[i];
7676+ col = {
7777+ key: colNode.getAttribute('data-col'),
7878+ sortable: !colNode.getAttribute('data-nosort'),
7979+ type: colNode.getAttribute('data-type') || 'string'
8080+ };
8181+ cols.push(col);
8282+ if (col.sortable) {
8383+ col.defaultDescSort = col.type === 'number';
8484+ colNode.innerHTML =
8585+ colNode.innerHTML + '<span class="sorter"></span>';
8686+ }
8787+ }
8888+ return cols;
8989+ }
9090+ // attaches a data attribute to every tr element with an object
9191+ // of data values keyed by column name
9292+ function loadRowData(tableRow) {
9393+ var tableCols = tableRow.querySelectorAll('td'),
9494+ colNode,
9595+ col,
9696+ data = {},
9797+ i,
9898+ val;
9999+ for (i = 0; i < tableCols.length; i += 1) {
100100+ colNode = tableCols[i];
101101+ col = cols[i];
102102+ val = colNode.getAttribute('data-value');
103103+ if (col.type === 'number') {
104104+ val = Number(val);
105105+ }
106106+ data[col.key] = val;
107107+ }
108108+ return data;
109109+ }
110110+ // loads all row data
111111+ function loadData() {
112112+ var rows = getTableBody().querySelectorAll('tr'),
113113+ i;
114114+115115+ for (i = 0; i < rows.length; i += 1) {
116116+ rows[i].data = loadRowData(rows[i]);
117117+ }
118118+ }
119119+ // sorts the table using the data for the ith column
120120+ function sortByIndex(index, desc) {
121121+ var key = cols[index].key,
122122+ sorter = function(a, b) {
123123+ a = a.data[key];
124124+ b = b.data[key];
125125+ return a < b ? -1 : a > b ? 1 : 0;
126126+ },
127127+ finalSorter = sorter,
128128+ tableBody = document.querySelector('.coverage-summary tbody'),
129129+ rowNodes = tableBody.querySelectorAll('tr'),
130130+ rows = [],
131131+ i;
132132+133133+ if (desc) {
134134+ finalSorter = function(a, b) {
135135+ return -1 * sorter(a, b);
136136+ };
137137+ }
138138+139139+ for (i = 0; i < rowNodes.length; i += 1) {
140140+ rows.push(rowNodes[i]);
141141+ tableBody.removeChild(rowNodes[i]);
142142+ }
143143+144144+ rows.sort(finalSorter);
145145+146146+ for (i = 0; i < rows.length; i += 1) {
147147+ tableBody.appendChild(rows[i]);
148148+ }
149149+ }
150150+ // removes sort indicators for current column being sorted
151151+ function removeSortIndicators() {
152152+ var col = getNthColumn(currentSort.index),
153153+ cls = col.className;
154154+155155+ cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
156156+ col.className = cls;
157157+ }
158158+ // adds sort indicators for current column being sorted
159159+ function addSortIndicators() {
160160+ getNthColumn(currentSort.index).className += currentSort.desc
161161+ ? ' sorted-desc'
162162+ : ' sorted';
163163+ }
164164+ // adds event listeners for all sorter widgets
165165+ function enableUI() {
166166+ var i,
167167+ el,
168168+ ithSorter = function ithSorter(i) {
169169+ var col = cols[i];
170170+171171+ return function() {
172172+ var desc = col.defaultDescSort;
173173+174174+ if (currentSort.index === i) {
175175+ desc = !currentSort.desc;
176176+ }
177177+ sortByIndex(i, desc);
178178+ removeSortIndicators();
179179+ currentSort.index = i;
180180+ currentSort.desc = desc;
181181+ addSortIndicators();
182182+ };
183183+ };
184184+ for (i = 0; i < cols.length; i += 1) {
185185+ if (cols[i].sortable) {
186186+ // add the click event handler on the th so users
187187+ // dont have to click on those tiny arrows
188188+ el = getNthColumn(i).querySelector('.sorter').parentElement;
189189+ if (el.addEventListener) {
190190+ el.addEventListener('click', ithSorter(i));
191191+ } else {
192192+ el.attachEvent('onclick', ithSorter(i));
193193+ }
194194+ }
195195+ }
196196+ }
197197+ // adds sorting functionality to the UI
198198+ return function() {
199199+ if (!getTable()) {
200200+ return;
201201+ }
202202+ cols = loadColumns();
203203+ loadData();
204204+ addSearchBox();
205205+ addSortIndicators();
206206+ enableUI();
207207+ };
208208+})();
209209+210210+window.addEventListener('load', addSorting);
apps/server/test/coverage-unit/lcov.info
This is a binary file and will not be displayed.
-36
packages/utils/index.js
···11-/**
22- * Throws an error with the given message.
33- * This is useful for creating never-returning functions that help with TypeScript's control flow analysis.
44- *
55- * @param message - The error message to throw
66- * @throws {Error} Always throws an error
77- * @returns {never} This function never returns
88- *
99- * @example
1010- * ```typescript
1111- * const user = getUser() ?? raise("User not found");
1212- * // TypeScript knows user is not null/undefined after this line
1313- * ```
1414- */
1515-export const raise = (message) => {
1616- throw new Error(message);
1717-};
1818-/**
1919- * Asserts that a condition is true, throwing an error if it's not.
2020- *
2121- * @param condition - The condition to assert
2222- * @param message - The error message to throw if condition is false
2323- * @throws {Error} Throws if condition is false
2424- * @returns {asserts condition} TypeScript assertion
2525- *
2626- * @example
2727- * ```typescript
2828- * assert(user !== null, "User must not be null");
2929- * // TypeScript knows user is not null after this line
3030- * ```
3131- */
3232-export const assert = (condition, message) => {
3333- if (!condition) {
3434- throw new Error(message);
3535- }
3636-};