because I got bored of customising my CV for every job
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(client): add utility functions for formatting and matching

+218
+6
apps/client/src/utils/cn.ts
··· 1 + import { type ClassValue, clsx } from "clsx"; 2 + import { twMerge } from "tailwind-merge"; 3 + 4 + export function cn(...inputs: ClassValue[]) { 5 + return twMerge(clsx(inputs)); 6 + }
+47
apps/client/src/utils/config.ts
··· 1 + /** 2 + * Shared configuration utilities for the client application 3 + */ 4 + 5 + /** 6 + * Get the server URL from environment variables with fallback 7 + */ 8 + export const getServerUrl = (): string => { 9 + const serverUrl = import.meta.env.VITE_SERVER_URL; 10 + 11 + if (!serverUrl) { 12 + return "http://localhost:3000"; 13 + } 14 + 15 + // Remove trailing slash if present 16 + return serverUrl.endsWith("/") ? serverUrl.slice(0, -1) : serverUrl; 17 + }; 18 + 19 + /** 20 + * Get the GraphQL endpoint URL 21 + */ 22 + export const getGraphQLEndpoint = (): string => { 23 + return `${getServerUrl()}/graphql`; 24 + }; 25 + 26 + /** 27 + * Parse server URL to extract components 28 + */ 29 + export const parseServerUrl = (url: string) => { 30 + try { 31 + const urlObj = new URL(url); 32 + return { 33 + protocol: urlObj.protocol.slice(0, -1), // Remove trailing ':' 34 + hostname: urlObj.hostname, 35 + port: urlObj.port || (url.startsWith("https") ? "443" : "80"), 36 + url: url, 37 + }; 38 + } catch { 39 + // Fallback for invalid URLs 40 + return { 41 + protocol: "http", 42 + hostname: "localhost", 43 + port: "3000", 44 + url: "http://localhost:3000", 45 + }; 46 + } 47 + };
+72
apps/client/src/utils/graphql-fetcher.ts
··· 1 + import { GraphQLClient } from "graphql-request"; 2 + 3 + const endpoint = import.meta.env.VITE_SERVER_URL 4 + ? `${import.meta.env.VITE_SERVER_URL}/graphql` 5 + : "http://localhost:3000/graphql"; 6 + 7 + // Store token and clearToken callback 8 + let currentToken: string | null = null; 9 + let clearTokenCallback: (() => void) | null = null; 10 + 11 + // Set token from TokenProvider 12 + export const setGraphQLToken = ( 13 + token: string | null, 14 + clearToken: () => void, 15 + ) => { 16 + currentToken = token; 17 + clearTokenCallback = clearToken; 18 + }; 19 + 20 + // GraphQL fetcher function for React Query codegen 21 + export const graphQLFetcher = 22 + <TData, TVariables>(query: string, variables?: TVariables) => 23 + async (): Promise<TData> => { 24 + const client = new GraphQLClient(endpoint); 25 + 26 + // Set authorization header if token exists 27 + if (currentToken) { 28 + client.setHeader("authorization", `Bearer ${currentToken}`); 29 + } 30 + 31 + try { 32 + return await client.request<TData>( 33 + query, 34 + variables as Record<string, unknown>, 35 + ); 36 + } catch (error) { 37 + // Handle GraphQL errors 38 + console.error("GraphQL request failed:", error); 39 + 40 + // Check if it's an authentication error (graphql-request error format) 41 + if (error && typeof error === "object") { 42 + // graphql-request wraps errors in a ClientError with response.errors 43 + const graphqlError = error as { 44 + response?: { 45 + status?: number; 46 + errors?: Array<{ 47 + message?: string; 48 + extensions?: { code?: string }; 49 + }>; 50 + }; 51 + }; 52 + 53 + // Check for 401 HTTP status or Unauthorized errors 54 + const isUnauthorized = 55 + graphqlError.response?.status === 401 || 56 + graphqlError.response?.errors?.some( 57 + (err) => 58 + err.message?.includes("Unauthorized") || 59 + err.message?.includes("Invalid token") || 60 + err.message?.includes("Token expired") || 61 + err.extensions?.code === "UNAUTHENTICATED", 62 + ); 63 + 64 + if (isUnauthorized && clearTokenCallback) { 65 + // Clear token on authentication errors 66 + clearTokenCallback(); 67 + } 68 + } 69 + 70 + throw error; 71 + } 72 + };
+37
apps/client/src/utils/salaryFormatter.ts
··· 1 + /** 2 + * Formats salary range for display 3 + * @param minSalary - Minimum salary value 4 + * @param maxSalary - Maximum salary value 5 + * @returns Formatted salary string or null if no valid salary data 6 + */ 7 + export const formatSalary = ( 8 + minSalary?: number | null, 9 + maxSalary?: number | null, 10 + ): string | null => { 11 + // If both are undefined/null, return null 12 + if (!(minSalary || maxSalary)) { 13 + return null; 14 + } 15 + 16 + // If minSalary === maxSalary, show just one value 17 + if (minSalary && maxSalary && minSalary === maxSalary) { 18 + return `$${minSalary.toLocaleString()}`; 19 + } 20 + 21 + // If both are defined and different, show range 22 + if (minSalary && maxSalary) { 23 + return `$${minSalary.toLocaleString()} - $${maxSalary.toLocaleString()}`; 24 + } 25 + 26 + // If only minSalary is defined, show with "+" 27 + if (minSalary) { 28 + return `$${minSalary.toLocaleString()}+`; 29 + } 30 + 31 + // If only maxSalary is defined, show "Up to" 32 + if (maxSalary) { 33 + return `Up to $${maxSalary.toLocaleString()}`; 34 + } 35 + 36 + return null; 37 + };
+44
apps/client/src/utils/skillsMatcher.ts
··· 1 + interface Skill { 2 + id: string; 3 + name: string; 4 + } 5 + 6 + export const calculateSkillsMatch = ( 7 + userSkills: Skill[], 8 + vacancySkills: Skill[], 9 + ): { 10 + percentage: number; 11 + matchedSkills: Skill[]; 12 + missingSkills: Skill[]; 13 + } => { 14 + if (vacancySkills.length === 0) { 15 + return { 16 + percentage: 0, 17 + matchedSkills: [], 18 + missingSkills: [], 19 + }; 20 + } 21 + 22 + // Create sets of skill IDs for efficient lookup 23 + const userSkillIds = new Set(userSkills.map((s) => s.id)); 24 + const _vacancySkillIds = new Set(vacancySkills.map((s) => s.id)); 25 + 26 + // Find matched and missing skills 27 + const matchedSkills = vacancySkills.filter((skill) => 28 + userSkillIds.has(skill.id), 29 + ); 30 + const missingSkills = vacancySkills.filter( 31 + (skill) => !userSkillIds.has(skill.id), 32 + ); 33 + 34 + // Calculate percentage 35 + const percentage = Math.round( 36 + (matchedSkills.length / vacancySkills.length) * 100, 37 + ); 38 + 39 + return { 40 + percentage, 41 + matchedSkills, 42 + missingSkills, 43 + }; 44 + };
+12
apps/client/src/utils/userUtils.ts
··· 1 + /** 2 + * Utility functions for user-related operations 3 + */ 4 + 5 + /** 6 + * Formats a user's name to get the first initial in uppercase 7 + * @param name - User's name 8 + * @returns First character of the name in uppercase 9 + */ 10 + export const formatName = (name: string): string => { 11 + return name.charAt(0).toUpperCase(); 12 + };