diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md deleted file mode 100644 index 61b2f0b..0000000 --- a/ARCHITECTURE.md +++ /dev/null @@ -1,57 +0,0 @@ -# Architecture Overview - -This project follows a three-layer architecture with clear separation of concerns and strong typing across boundaries. - -## Layers - -1. GraphQL (API Layer) - - Location: `apps/server/src/modules/**` - - Implements resolvers and GraphQL object types (classes) - - GraphQL classes expose `fromDomain()` static factories to convert domain entities to API types - - No database access; no business logic - -2. Domain (Core Model Layer) - - Location: `apps/server/src/modules/**/*.entity.ts` - - Plain classes representing core business entities - - Encapsulate business rules where appropriate - - No framework-specific decorators except GraphQL field metadata on API classes - -3. Persistence (Prisma Layer) - - Location: Prisma client via `PrismaService` - - No domain logic; pure data access - -## Mapping Pattern - -- Each entity has a dedicated Mapper service implementing `BaseMapper` -- Mappers are `@Injectable()` Nest providers -- Services inject mappers and use them to convert from Prisma models to domain entities -- GraphQL classes convert domain entities via `fromDomain()` when returning data - -Example flow: Resolver -> Service -> Prisma -> Mapper.toDomain -> Domain -> GraphQLType.fromDomain - -## Conventions - -- Services accept and return domain entities (never Prisma models) -- Mappers live alongside their entity modules, e.g. `organization.mapper.ts`, `vacancy.mapper.ts` -- Modules must register mapper providers and export them if shared -- Prefer constructor-based instantiation; avoid non-null assertions -- Use bracket notation for `process.env["VAR"]` and `prisma["model"]` -- Linting and formatting are enforced via Biome with `$schema` validation - -## Current Mapper Implementations - -- Auth: `user.mapper.ts` (already injectable) -- Organization: `organization.mapper.ts` (injectable and used by `organization.service.ts`) -- Organization Roles: `organization-role.mapper.ts` (injectable and used by `organization-role.service.ts`) -- Vacancies: `vacancy.mapper.ts` (injectable and used by `vacancy.service.ts`) -- Job Experience submodules (company/level/role/skill) include injectable mappers - -## Docker & Tooling - -- Node 22 for all images (client, server, compose client service) -- Biome 2.2.6 with `$schema` and `files.includes` exclusions for `node_modules`, `dist`, `build` - -## Testing - -- E2E tests under `apps/server/test` exercise GraphQL endpoints against a test app -- Tests expect services to return proper domain entities; GraphQL types handle API shaping diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index a62dbd9..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,68 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -### Added -- Initial project setup with NestJS backend and React frontend -- Docker containerization with docker-compose -- GraphQL API with Apollo Server -- Authentication system with JWT tokens -- Database schema with Prisma ORM -- User management and organization features -- Job experience tracking system -- Company, role, level, and skill management -- Toast notification system -- Error boundary components -- TypeScript configuration with strict settings -- Biome linting and formatting configuration - -### Changed -- Improved configuration management using `getOrThrow()` instead of manual error throwing -- Enhanced type safety by prohibiting non-null assertion operators (`!`) -- Updated entity instantiation patterns to use constructor-based initialization -- Improved error handling patterns throughout the codebase - -### Fixed -- Configuration validation issues -- Type safety improvements -- Build process optimization - -### Security -- Implemented secure JWT authentication -- Added proper environment variable validation -- Enhanced input validation with Zod schemas - -## [0.1.0] - 2025-01-19 - -### Added -- Initial project structure -- Basic authentication flow -- Core database entities -- GraphQL schema definition -- Docker development environment - ---- - -## Development Guidelines - -### Commit Convention -This project uses [Conventional Commits](https://www.conventionalcommits.org/) format: - -- `feat:` A new feature -- `fix:` A bug fix -- `docs:` Documentation only changes -- `style:` Changes that do not affect the meaning of the code -- `refactor:` A code change that neither fixes a bug nor adds a feature -- `perf:` A code change that improves performance -- `test:` Adding missing tests or correcting existing tests -- `chore:` Changes to the build process or auxiliary tools - -### Versioning -- **MAJOR** version when you make incompatible API changes -- **MINOR** version when you add functionality in a backwards compatible manner -- **PATCH** version when you make backwards compatible bug fixes diff --git a/ROADMAP.md b/ROADMAP.md deleted file mode 100644 index 9459100..0000000 --- a/ROADMAP.md +++ /dev/null @@ -1,209 +0,0 @@ -# Roadmap - -This document outlines the planned features and improvements for the CV Generator project. Items are organized by topic and prioritized for development. - -## 🏢 B2C CV Builder Functionality - -### Core Features -- [ ] **CV Templates & Design System** - - [ ] Create responsive CV templates - - [ ] Implement design customization options - - [ ] Add theme selection (professional, creative, minimal) - - [ ] Support for multiple CV formats (chronological, functional, hybrid) - -- [ ] **Content Management** - - [ ] Rich text editor for CV sections - - [ ] Drag-and-drop section reordering - - [ ] Real-time preview functionality - - [ ] Auto-save functionality - - [ ] Version history and rollback - -- [ ] **Export & Sharing** - - [ ] PDF export with high-quality rendering - - [ ] Multiple file format support (PDF, DOCX, HTML) - - [ ] Shareable CV links - - [ ] QR code generation for CV sharing - - [ ] Social media integration - -### Advanced Features -- [ ] **AI-Powered Enhancements** - - [ ] AI content suggestions and improvements - - [ ] Keyword optimization for ATS systems - - [ ] Skills gap analysis - - [ ] Industry-specific recommendations - -- [ ] **Analytics & Insights** - - [ ] CV view tracking and analytics - - [ ] Performance metrics dashboard - - [ ] ATS compatibility scoring - - [ ] Improvement suggestions - -### User Experience -- [ ] **Onboarding & Tutorials** - - [ ] Interactive CV creation wizard - - [ ] Step-by-step guidance system - - [ ] Sample CV library - - [ ] Video tutorials and help center - -- [ ] **Mobile Experience** - - [ ] Mobile-responsive design - - [ ] Progressive Web App (PWA) features - - [ ] Offline editing capabilities - - [ ] Mobile-optimized export options - ---- - -## 🏢 B2B Solution for Managing Groups of Employees - -### Organization Management -- [ ] **Multi-tenant Architecture** - - [ ] Organization hierarchy management - - [ ] Role-based access control (RBAC) - - [ ] Department and team structures - - [ ] Custom organization branding - -- [ ] **Employee Management** - - [ ] Bulk employee import/export - - [ ] Employee profile management - - [ ] Skills inventory and tracking - - [ ] Performance review integration - - [ ] Employee directory with search and filtering - -### HR Analytics & Reporting -- [ ] **Workforce Analytics** - - [ ] Skills gap analysis across organization - - [ ] Employee development tracking - - [ ] Career progression insights - - [ ] Diversity and inclusion metrics - - [ ] Retention and turnover analysis - -- [ ] **Reporting Dashboard** - - [ ] Custom report builder - - [ ] Scheduled report delivery - - [ ] Executive summary dashboards - - [ ] Compliance reporting - - [ ] Export capabilities (PDF, Excel, CSV) - -### Integration & Automation -- [ ] **HR System Integrations** - - [ ] ATS (Applicant Tracking System) integration - - [ ] HRIS (Human Resource Information System) sync - - [ ] Learning Management System (LMS) integration - - [ ] Performance management system integration - - [ ] Single Sign-On (SSO) support - -- [ ] **Workflow Automation** - - [ ] Automated CV updates and reminders - - [ ] Approval workflows for CV changes - - [ ] Notification system for managers - - [ ] Bulk operations and batch processing - - [ ] API for custom integrations - -### Compliance & Security -- [ ] **Data Privacy & Security** - - [ ] GDPR compliance features - - [ ] Data encryption at rest and in transit - - [ ] Audit logging and trail - - [ ] Data retention policies - - [ ] Backup and disaster recovery - -- [ ] **Access Control** - - [ ] Granular permission system - - [ ] Multi-factor authentication (MFA) - - [ ] Session management and timeout - - [ ] IP whitelisting and restrictions - - [ ] Admin console for system management - ---- - -## 📈 Advertising & Monetization Options - -### Freemium Model -- [ ] **Free Tier Features** - - [ ] Basic CV templates (3-5 options) - - [ ] Standard export formats (PDF only) - - [ ] Limited customization options - - [ ] Basic analytics and insights - - [ ] Community support - -- [ ] **Premium Tier Features** - - [ ] Unlimited template access - - [ ] Advanced customization tools - - [ ] Multiple export formats (PDF, DOCX, HTML) - - [ ] AI-powered content suggestions - - [ ] Advanced analytics and ATS optimization - - [ ] Priority customer support - -### Enterprise Solutions -- [ ] **B2B Subscription Tiers** - - [ ] Small business package (up to 50 employees) - - [ ] Medium enterprise package (up to 500 employees) - - [ ] Large enterprise package (unlimited employees) - - [ ] Custom enterprise solutions with dedicated support - -### Revenue Streams -- [ ] **Direct Monetization** - - [ ] Subscription-based pricing models - - [ ] Pay-per-use CV generation - - [ ] Premium template marketplace - - [ ] White-label solutions for partners - -- [ ] **Partnership & Integration Revenue** - - [ ] Job board partnerships and referrals - - [ ] Recruitment agency integrations - - [ ] HR software marketplace listings - - [ ] Affiliate marketing programs - -### Marketing & Growth -- [ ] **User Acquisition** - - [ ] SEO optimization for CV-related keywords - - [ ] Content marketing strategy (CV tips, career advice) - - [ ] Social media presence and engagement - - [ ] Influencer partnerships and collaborations - - [ ] Referral program implementation - -- [ ] **Retention & Engagement** - - [ ] Email marketing campaigns - - [ ] In-app notifications and tips - - [ ] Gamification elements (achievements, progress tracking) - - [ ] Community features and forums - - [ ] Regular feature updates and improvements - ---- - -## 🛠 Technical Infrastructure - -### Platform & Scalability -- [ ] **Performance Optimization** - - [ ] Database query optimization - - [ ] Caching strategies (Redis implementation) - - [ ] CDN integration for static assets - - [ ] Load balancing and auto-scaling - - [ ] Performance monitoring and alerting - -- [ ] **DevOps & Deployment** - - [ ] CI/CD pipeline optimization - - [ ] Automated testing (unit, integration, e2e) - - [ ] Infrastructure as Code (IaC) - - [ ] Blue-green deployment strategy - - [ ] Monitoring and logging solutions - -### Security & Compliance -- [ ] **Security Enhancements** - - [ ] Penetration testing and security audits - - [ ] Vulnerability scanning and management - - [ ] Security headers and CSP implementation - - [ ] Rate limiting and DDoS protection - - [ ] Security incident response plan - -### Data & Analytics -- [ ] **Data Infrastructure** - - [ ] Data warehouse implementation - - [ ] ETL processes for analytics - - [ ] Real-time analytics pipeline - - [ ] Data visualization tools - - [ ] Machine learning model deployment - ---- - -*This roadmap is a living document and will be updated regularly based on user feedback, market research, and business priorities.* diff --git a/apps/client/src/components/icons/CloseIcon.tsx b/apps/client/src/components/icons/CloseIcon.tsx deleted file mode 100644 index 35eca01..0000000 --- a/apps/client/src/components/icons/CloseIcon.tsx +++ /dev/null @@ -1,25 +0,0 @@ -interface CloseIconProps { - className?: string; -} - -/** - * Close icon component - */ -export default function CloseIcon({ className = "w-4 h-4" }: CloseIconProps) { - return ( - - Close icon - - - ); -} diff --git a/apps/client/src/components/icons/DeleteIcon.tsx b/apps/client/src/components/icons/DeleteIcon.tsx deleted file mode 100644 index ca90145..0000000 --- a/apps/client/src/components/icons/DeleteIcon.tsx +++ /dev/null @@ -1,25 +0,0 @@ -interface DeleteIconProps { - className?: string; -} - -/** - * Delete icon component - */ -export default function DeleteIcon({ className = "w-4 h-4" }: DeleteIconProps) { - return ( - - Delete icon - - - ); -} diff --git a/apps/client/src/components/icons/DocumentIcon.tsx b/apps/client/src/components/icons/DocumentIcon.tsx deleted file mode 100644 index ce4d4c4..0000000 --- a/apps/client/src/components/icons/DocumentIcon.tsx +++ /dev/null @@ -1,19 +0,0 @@ -export default function DocumentIcon() { - return ( - - Document - - - ); -} diff --git a/apps/client/src/components/icons/EditIcon.tsx b/apps/client/src/components/icons/EditIcon.tsx deleted file mode 100644 index 970d2d7..0000000 --- a/apps/client/src/components/icons/EditIcon.tsx +++ /dev/null @@ -1,25 +0,0 @@ -interface EditIconProps { - className?: string; -} - -/** - * Edit icon component - */ -export default function EditIcon({ className = "w-4 h-4" }: EditIconProps) { - return ( - - Edit icon - - - ); -} diff --git a/apps/client/src/components/icons/ErrorIcon.tsx b/apps/client/src/components/icons/ErrorIcon.tsx deleted file mode 100644 index 5069391..0000000 --- a/apps/client/src/components/icons/ErrorIcon.tsx +++ /dev/null @@ -1,19 +0,0 @@ -interface ErrorIconProps { - className?: string; -} - -/** - * Error icon component - */ -export default function ErrorIcon({ className = "w-5 h-5" }: ErrorIconProps) { - return ( - - Error icon - - - ); -} diff --git a/apps/client/src/components/icons/LinkIcon.tsx b/apps/client/src/components/icons/LinkIcon.tsx deleted file mode 100644 index 40069ed..0000000 --- a/apps/client/src/components/icons/LinkIcon.tsx +++ /dev/null @@ -1,19 +0,0 @@ -export default function LinkIcon() { - return ( - - Link - - - ); -} diff --git a/apps/client/src/components/icons/LoadingIcon.tsx b/apps/client/src/components/icons/LoadingIcon.tsx deleted file mode 100644 index 70f9ee5..0000000 --- a/apps/client/src/components/icons/LoadingIcon.tsx +++ /dev/null @@ -1,33 +0,0 @@ -interface LoadingIconProps { - className?: string; -} - -/** - * Loading spinner icon component - */ -export default function LoadingIcon({ - className = "w-4 h-4", -}: LoadingIconProps) { - return ( - - Loading icon - - - - ); -} diff --git a/apps/client/src/components/icons/ToastIcon.tsx b/apps/client/src/components/icons/ToastIcon.tsx deleted file mode 100644 index 28a7536..0000000 --- a/apps/client/src/components/icons/ToastIcon.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { cva, type VariantProps } from "class-variance-authority"; - -const iconVariants = cva("w-5 h-5", { - variants: { - level: { - success: "text-green-600", - warning: "text-yellow-600", - info: "text-blue-600", - error: "text-red-600", - }, - }, - defaultVariants: { - level: "info", - }, -}); - -interface ToastIconProps extends VariantProps { - level: "success" | "warning" | "info" | "error"; -} - -/** - * Toast icon component with CVA styling - */ -export default function ToastIcon({ level }: ToastIconProps) { - const iconProps = { - className: iconVariants({ level }), - fill: "none", - stroke: "currentColor", - viewBox: "0 0 24 24", - }; - - return ( - - {level.charAt(0).toUpperCase() + level.slice(1)} icon - - - ); -} - -/** - * Get the SVG path for the given toast level - */ -function getIconPath(level: "success" | "warning" | "info" | "error"): string { - const iconPaths = { - success: "M5 13l4 4L19 7", - warning: - "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z", - info: "M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z", - error: "M6 18L18 6M6 6l12 12", - }; - - return iconPaths[level]; -} diff --git a/apps/client/src/components/icons/UploadIcon.tsx b/apps/client/src/components/icons/UploadIcon.tsx deleted file mode 100644 index 3f7c8bd..0000000 --- a/apps/client/src/components/icons/UploadIcon.tsx +++ /dev/null @@ -1,19 +0,0 @@ -export default function UploadIcon() { - return ( - - Upload - - - ); -} diff --git a/apps/client/src/components/icons/index.ts b/apps/client/src/components/icons/index.ts deleted file mode 100644 index a97b8dd..0000000 --- a/apps/client/src/components/icons/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export { default as CloseIcon } from "./CloseIcon"; -export { default as DeleteIcon } from "./DeleteIcon"; -export { default as DocumentIcon } from "./DocumentIcon"; -export { default as EditIcon } from "./EditIcon"; -export { default as ErrorIcon } from "./ErrorIcon"; -export { default as LinkIcon } from "./LinkIcon"; -export { default as LoadingIcon } from "./LoadingIcon"; -export { default as ToastIcon } from "./ToastIcon"; -export { default as UploadIcon } from "./UploadIcon"; diff --git a/apps/client/src/constants/auth.ts b/apps/client/src/constants/auth.ts index 32d4af4..a27a3bb 100644 --- a/apps/client/src/constants/auth.ts +++ b/apps/client/src/constants/auth.ts @@ -1,2 +1,4 @@ export const AUTH_TOKEN_KEY = "auth_token" as const; +export const AUTH_REFRESH_TOKEN_KEY = "auth_refresh_token" as const; +export const AUTH_TOKEN_EXPIRY_KEY = "auth_token_expiry" as const; export const AUTH_USER_QUERY_KEY = ["auth", "user"] as const; diff --git a/apps/client/src/features/app/queries/app.graphql b/apps/client/src/features/app/queries/app.graphql index 46b1f13..6443569 100644 --- a/apps/client/src/features/app/queries/app.graphql +++ b/apps/client/src/features/app/queries/app.graphql @@ -1,7 +1,3 @@ -query Hello { - hello -} - query Health { health { status diff --git a/apps/client/src/features/auth/LoginForm.tsx b/apps/client/src/features/auth/LoginForm.tsx deleted file mode 100644 index c33c6e7..0000000 --- a/apps/client/src/features/auth/LoginForm.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useState } from "react"; -import { Link } from "react-router-dom"; -import { useLogin } from "@/hooks/useAuth"; -import Button from "@/ui/Button"; -import TextInput from "@/ui/TextInput"; -import AuthForm from "./AuthForm"; - -export default function LoginForm() { - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [loginMutation, { loading, error }] = useLogin(); - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - loginMutation({ - variables: { - email, - password, - }, - }); - }; - - return ( - -
- - - -

- No account yet?{" "} - - - -

- -
- ); -} diff --git a/apps/client/src/features/auth/RegisterForm.tsx b/apps/client/src/features/auth/RegisterForm.tsx deleted file mode 100644 index 783442a..0000000 --- a/apps/client/src/features/auth/RegisterForm.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { useState } from "react"; -import { Link } from "react-router-dom"; -import { useRegister } from "@/hooks/useAuth"; -import Button from "@/ui/Button"; -import TextInput from "@/ui/TextInput"; -import AuthForm from "./AuthForm"; - -export default function RegisterForm() { - const [name, setName] = useState(""); - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [register, { loading, error }] = useRegister(); - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - register({ - variables: { - name, - email, - password, - }, - }); - }; - - return ( - -
- - - - -

- Already have an account?{" "} - - - -

- -
- ); -} diff --git a/apps/client/src/features/job-experience/components/index.ts b/apps/client/src/features/job-experience/components/index.ts index fcf3fb3..1fe0dea 100644 --- a/apps/client/src/features/job-experience/components/index.ts +++ b/apps/client/src/features/job-experience/components/index.ts @@ -1,11 +1,11 @@ /** * Job Experience feature components */ -export { default as JobExperienceCard } from "./JobExperienceCard"; +export { JobExperienceCard } from "./JobExperienceCard"; export { JobExperienceCreationSelector } from "./JobExperienceCreationSelector"; -export { default as JobExperienceEmpty } from "./JobExperienceEmpty"; +export { JobExperienceEmpty } from "./JobExperienceEmpty"; export { JobExperienceForm } from "./JobExperienceForm"; -export { default as JobExperienceHeader } from "./JobExperienceHeader"; -export { default as JobExperienceList } from "./JobExperienceList"; -export { default as JobExperienceLoading } from "./JobExperienceLoading"; +export { JobExperienceHeader } from "./JobExperienceHeader"; +export { JobExperienceList } from "./JobExperienceList"; +export { JobExperienceLoading } from "./JobExperienceLoading"; export { JobExperienceTable } from "./JobExperienceTable"; diff --git a/apps/client/src/features/job-experience/queries/companies-query.graphql b/apps/client/src/features/job-experience/queries/companies-query.graphql index 1766372..67e09d6 100644 --- a/apps/client/src/features/job-experience/queries/companies-query.graphql +++ b/apps/client/src/features/job-experience/queries/companies-query.graphql @@ -1,9 +1,13 @@ query Companies { companies { - id - name - description - createdAt - updatedAt + edges { + node { + id + name + description + createdAt + updatedAt + } + } } } diff --git a/apps/client/src/features/job-experience/queries/job-experience-form-data.graphql b/apps/client/src/features/job-experience/queries/job-experience-form-data.graphql index b44ab13..2875784 100644 --- a/apps/client/src/features/job-experience/queries/job-experience-form-data.graphql +++ b/apps/client/src/features/job-experience/queries/job-experience-form-data.graphql @@ -1,30 +1,46 @@ query JobExperienceFormData { companies { - id - name - description - createdAt - updatedAt + edges { + node { + id + name + description + createdAt + updatedAt + } + } } roles { - id - name - description - createdAt - updatedAt + edges { + node { + id + name + description + createdAt + updatedAt + } + } } levels { - id - name - description - createdAt - updatedAt + edges { + node { + id + name + description + createdAt + updatedAt + } + } } skills { - id - name - description - createdAt - updatedAt + edges { + node { + id + name + description + createdAt + updatedAt + } + } } } diff --git a/apps/client/src/features/job-experience/queries/levels-query.graphql b/apps/client/src/features/job-experience/queries/levels-query.graphql index dc82382..c49f348 100644 --- a/apps/client/src/features/job-experience/queries/levels-query.graphql +++ b/apps/client/src/features/job-experience/queries/levels-query.graphql @@ -1,9 +1,13 @@ query Levels { levels { - id - name - description - createdAt - updatedAt + edges { + node { + id + name + description + createdAt + updatedAt + } + } } } diff --git a/apps/client/src/features/job-experience/queries/me-job-experience.graphql b/apps/client/src/features/job-experience/queries/me-job-experience.graphql index 64d1790..5996a7a 100644 --- a/apps/client/src/features/job-experience/queries/me-job-experience.graphql +++ b/apps/client/src/features/job-experience/queries/me-job-experience.graphql @@ -1,25 +1,38 @@ query MeJobExperience { - myEmploymentHistory { - id - startDate - endDate - description - company { - id - name - website - } - role { - id - name - } - level { - id - name - } - skills { - id - name + me { + experience { + edges { + node { + id + startDate + endDate + description + company { + id + name + website + } + role { + id + name + } + level { + id + name + } + skills { + id + name + } + } + } + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount } } } diff --git a/apps/client/src/features/job-experience/queries/roles-query.graphql b/apps/client/src/features/job-experience/queries/roles-query.graphql index 4a974f1..7336ea5 100644 --- a/apps/client/src/features/job-experience/queries/roles-query.graphql +++ b/apps/client/src/features/job-experience/queries/roles-query.graphql @@ -1,9 +1,13 @@ query Roles { roles { - id - name - description - createdAt - updatedAt + edges { + node { + id + name + description + createdAt + updatedAt + } + } } } diff --git a/apps/client/src/features/job-experience/queries/skills-query.graphql b/apps/client/src/features/job-experience/queries/skills-query.graphql index 5afaf74..04a31ea 100644 --- a/apps/client/src/features/job-experience/queries/skills-query.graphql +++ b/apps/client/src/features/job-experience/queries/skills-query.graphql @@ -1,9 +1,21 @@ -query Skills { - skills { - id - name - description - createdAt - updatedAt +query Skills($first: Int, $after: String, $searchTerm: String) { + skills(first: $first, after: $after, searchTerm: $searchTerm) { + edges { + cursor + node { + id + name + description + createdAt + updatedAt + } + } + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount } } diff --git a/apps/client/src/features/organizations/components/index.ts b/apps/client/src/features/organizations/components/index.ts index 89c49d4..45c52b1 100644 --- a/apps/client/src/features/organizations/components/index.ts +++ b/apps/client/src/features/organizations/components/index.ts @@ -1,3 +1,4 @@ +export { CollapsibleOrganizationTable } from "./CollapsibleOrganizationTable"; export { MembersTableBody } from "./MembersTableBody"; export { MembersTableHeader } from "./MembersTableHeader"; export { OrganizationMemberRow } from "./OrganizationMemberRow"; diff --git a/apps/client/src/features/user/queries/me-with-organizations.graphql b/apps/client/src/features/user/queries/me-with-organizations.graphql deleted file mode 100644 index 5ec9838..0000000 --- a/apps/client/src/features/user/queries/me-with-organizations.graphql +++ /dev/null @@ -1,54 +0,0 @@ -query MeWithOrganizations { - me { - id - email - name - createdAt - organizations { - id - name - description - createdAt - updatedAt - users { - id - joinedAt - role { - id - name - description - color - } - user { - id - name - email - createdAt - experience { - id - startDate - endDate - description - company { - id - name - website - } - role { - id - name - } - level { - id - name - } - skills { - id - name - } - } - } - } - } - } -} diff --git a/apps/client/src/lib/apollo-client.ts b/apps/client/src/lib/apollo-client.ts deleted file mode 100644 index ea26eb8..0000000 --- a/apps/client/src/lib/apollo-client.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ApolloClient, createHttpLink, InMemoryCache } from "@apollo/client"; -import { setContext } from "@apollo/client/link/context"; -import { AUTH_TOKEN_KEY } from "@/constants/auth"; -import { getGraphQLEndpoint } from "./config"; - -const httpLink = createHttpLink({ - uri: getGraphQLEndpoint(), -}); - -const authLink = setContext((_, { headers }) => { - const token = localStorage.getItem(AUTH_TOKEN_KEY); - return { - headers: { - ...headers, - authorization: token ? `Bearer ${token}` : "", - }, - }; -}); - -export const apolloClient = new ApolloClient({ - link: authLink.concat(httpLink), - cache: new InMemoryCache(), -}); diff --git a/apps/client/src/lib/config.ts b/apps/client/src/lib/config.ts deleted file mode 100644 index cc61a5d..0000000 --- a/apps/client/src/lib/config.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Shared configuration utilities for the client application - */ - -/** - * Get the server URL from environment variables with fallback - */ -export const getServerUrl = (): string => { - const serverUrl = import.meta.env["VITE_SERVER_URL"]; - - if (!serverUrl) { - return "http://localhost:3000"; - } - - // Remove trailing slash if present - return serverUrl.endsWith("/") ? serverUrl.slice(0, -1) : serverUrl; -}; - -/** - * Get the GraphQL endpoint URL - */ -export const getGraphQLEndpoint = (): string => { - return `${getServerUrl()}/graphql`; -}; - -/** - * Parse server URL to extract components - */ -export const parseServerUrl = (url: string) => { - try { - const urlObj = new URL(url); - return { - protocol: urlObj.protocol.slice(0, -1), // Remove trailing ':' - hostname: urlObj.hostname, - port: urlObj.port || (url.startsWith("https") ? "443" : "80"), - url: url, - }; - } catch { - // Fallback for invalid URLs - return { - protocol: "http", - hostname: "localhost", - port: "3000", - url: "http://localhost:3000", - }; - } -}; diff --git a/apps/client/src/ui/Badge.tsx b/apps/client/src/ui/Badge.tsx deleted file mode 100644 index fb710e3..0000000 --- a/apps/client/src/ui/Badge.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { cva, type VariantProps } from "class-variance-authority"; -import type { ReactNode } from "react"; - -export type CatppuccinColor = - | "ctp-red" - | "ctp-orange" - | "ctp-yellow" - | "ctp-green" - | "ctp-teal" - | "ctp-sky" - | "ctp-sapphire" - | "ctp-blue" - | "ctp-lavender" - | "ctp-mauve" - | "ctp-pink" - | "ctp-maroon" - | "ctp-peach" - | "ctp-rosewater" - | "ctp-gray"; - -const badgeVariants = cva( - "inline-flex items-center rounded-full px-2 py-1 text-xs font-medium text-white whitespace-nowrap", - { - variants: { - color: { - "ctp-red": "bg-ctp-red", - "ctp-orange": "bg-ctp-orange", - "ctp-yellow": "bg-ctp-yellow", - "ctp-green": "bg-ctp-green", - "ctp-teal": "bg-ctp-teal", - "ctp-sky": "bg-ctp-sky", - "ctp-sapphire": "bg-ctp-sapphire", - "ctp-blue": "bg-ctp-blue", - "ctp-lavender": "bg-ctp-lavender", - "ctp-mauve": "bg-ctp-mauve", - "ctp-pink": "bg-ctp-pink", - "ctp-maroon": "bg-ctp-maroon", - "ctp-peach": "bg-ctp-peach", - "ctp-rosewater": "bg-ctp-rosewater", - "ctp-gray": "bg-ctp-gray", - }, - }, - defaultVariants: { - color: "ctp-blue", - }, - }, -); - -type BadgeProps = { - children: ReactNode; - color?: CatppuccinColor; -} & Omit, "color">; - -export default function Badge({ - children, - color, - className, -}: BadgeProps & { className?: string }) { - return ( - {children} - ); -} diff --git a/apps/client/src/ui/Button.tsx b/apps/client/src/ui/Button.tsx deleted file mode 100644 index 9eaddb9..0000000 --- a/apps/client/src/ui/Button.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { cva, type VariantProps } from "class-variance-authority"; -import type { ButtonHTMLAttributes, PropsWithChildren, ReactNode } from "react"; - -const buttonVariants = cva( - "inline-flex items-center justify-center rounded px-4 py-2 text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2", - { - variants: { - variant: { - primary: - "bg-ctp-mauve text-ctp-base hover:bg-ctp-mauve/90 focus:ring-ctp-mauve", - secondary: - "bg-ctp-surface1 text-ctp-text hover:bg-ctp-surface2 focus:ring-ctp-surface1", - ghost: "text-ctp-text hover:bg-ctp-surface1 focus:ring-ctp-surface1", - outline: - "border border-ctp-surface0 text-ctp-text hover:bg-ctp-surface0 focus:ring-ctp-surface0", - destructive: - "bg-ctp-red text-ctp-base hover:bg-ctp-red/90 focus:ring-ctp-red", - }, - size: { - sm: "px-3 py-1.5 text-xs", - md: "px-4 py-2 text-sm", - lg: "px-6 py-3 text-base", - }, - }, - defaultVariants: { - variant: "primary", - size: "md", - }, - }, -); - -type ButtonProps = PropsWithChildren< - ButtonHTMLAttributes & - VariantProps & { - leftIcon?: ReactNode; - rightIcon?: ReactNode; - } ->; - -export default function Button({ - children, - className = "", - variant, - size, - leftIcon, - rightIcon, - ...props -}: ButtonProps) { - return ( - - ); -} diff --git a/apps/client/src/ui/Checkbox.tsx b/apps/client/src/ui/Checkbox.tsx deleted file mode 100644 index fbe65bc..0000000 --- a/apps/client/src/ui/Checkbox.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { cva, type VariantProps } from "class-variance-authority"; -import type { InputHTMLAttributes } from "react"; - -const checkboxVariants = cva( - "h-4 w-4 rounded border border-ctp-surface1 bg-ctp-surface0 text-ctp-mauve focus:ring-2 focus:ring-ctp-mauve focus:ring-offset-1 transition-colors", - { - variants: { - state: { - default: "", - error: "border-ctp-red focus:ring-ctp-red", - success: "border-ctp-green focus:ring-ctp-green", - }, - }, - defaultVariants: { - state: "default", - }, - }, -); - -type CheckboxProps = { - label: string; - error?: string; - onChange?: (checked: boolean) => void; -} & Omit, "onChange" | "type"> & - VariantProps; - -export default function Checkbox({ - label, - className = "", - error, - onChange, - id, - ...props -}: CheckboxProps) { - const handleChange = (e: React.ChangeEvent) => { - onChange?.(e.target.checked); - }; - - const inputId = id || `checkbox-${Math.random().toString(36).substr(2, 9)}`; - - return ( -
-
- - -
- {error &&

{error}

} -
- ); -} diff --git a/apps/client/src/ui/IconButton.tsx b/apps/client/src/ui/IconButton.tsx deleted file mode 100644 index 2a4086c..0000000 --- a/apps/client/src/ui/IconButton.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { cva, type VariantProps } from "class-variance-authority"; -import type { ButtonHTMLAttributes, ReactNode } from "react"; - -const iconButtonVariants = cva( - "inline-flex items-center justify-center rounded transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2", - { - variants: { - variant: { - primary: - "bg-ctp-mauve text-ctp-base hover:bg-ctp-mauve/90 focus:ring-ctp-mauve", - secondary: - "bg-ctp-surface1 text-ctp-text hover:bg-ctp-surface2 focus:ring-ctp-surface1", - ghost: "text-ctp-text hover:bg-ctp-surface1 focus:ring-ctp-surface1", - outline: - "border border-ctp-surface0 text-ctp-text hover:bg-ctp-surface0 focus:ring-ctp-surface0", - destructive: - "bg-ctp-red text-ctp-base hover:bg-ctp-red/90 focus:ring-ctp-red", - }, - size: { - sm: "p-1.5", - md: "p-2", - lg: "p-3", - }, - }, - defaultVariants: { - variant: "ghost", - size: "md", - }, - }, -); - -type IconButtonProps = ButtonHTMLAttributes & - VariantProps & { - icon: ReactNode; - label: string; - }; - -/** - * Icon-only button component for actions like edit, delete, etc. - */ -export default function IconButton({ - icon, - label, - className = "", - variant, - size, - ...props -}: IconButtonProps) { - return ( - - ); -} diff --git a/apps/client/src/ui/Select.tsx b/apps/client/src/ui/Select.tsx deleted file mode 100644 index 70d3aa9..0000000 --- a/apps/client/src/ui/Select.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { forwardRef } from "react"; - -interface SelectOption { - value: string; - label: string; -} - -interface SelectProps { - id?: string; - label?: string; - options: SelectOption[]; - value: string; - onChange: (value: string) => void; - placeholder?: string; - required?: boolean; - disabled?: boolean; - className?: string; - error?: string | undefined; -} - -export const Select = forwardRef( - ( - { - id, - label, - options, - value, - onChange, - placeholder, - required = false, - disabled = false, - className = "", - error, - }, - ref, - ) => { - const inputId = id || `select-${Math.random().toString(36).substr(2, 9)}`; - - return ( -
- {label && ( - - )} - - - - {error &&

{error}

} -
- ); - }, -); - -Select.displayName = "Select"; diff --git a/apps/client/src/ui/StatusBadge.tsx b/apps/client/src/ui/StatusBadge.tsx deleted file mode 100644 index 8411833..0000000 --- a/apps/client/src/ui/StatusBadge.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { cva } from "class-variance-authority"; - -const statusBadgeVariants = cva("px-2 py-1 text-xs rounded-full font-medium", { - variants: { - status: { - active: "bg-ctp-green text-white", - inactive: "bg-ctp-red text-white", - }, - }, - defaultVariants: { - status: "inactive", - }, -}); - -interface StatusBadgeProps { - isActive: boolean; - activeLabel?: string; - inactiveLabel?: string; - className?: string; -} - -export const StatusBadge = ({ - isActive, - activeLabel = "Active", - inactiveLabel = "Inactive", - className = "", -}: StatusBadgeProps) => { - return ( - - {isActive ? activeLabel : inactiveLabel} - - ); -}; diff --git a/apps/client/src/ui/Table.tsx b/apps/client/src/ui/Table.tsx deleted file mode 100644 index bc5a858..0000000 --- a/apps/client/src/ui/Table.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import type { ReactNode } from "react"; - -interface TableProps { - children: ReactNode; - className?: string; -} - -interface TableHeaderProps { - children: ReactNode; - className?: string; -} - -interface TableBodyProps { - children: ReactNode; - className?: string; -} - -interface TableRowProps { - children: ReactNode; - className?: string; - onClick?: () => void; -} - -interface TableCellProps { - children: ReactNode; - className?: string; - colSpan?: number; -} - -interface TableHeaderCellProps { - children: ReactNode; - className?: string; - colSpan?: number; -} - -export const Table = ({ children, className = "" }: TableProps) => { - return ( -
- - {children} -
-
- ); -}; - -export const TableHeader = ({ children, className = "" }: TableHeaderProps) => { - return {children}; -}; - -export const TableBody = ({ children, className = "" }: TableBodyProps) => { - return ( - - {children} - - ); -}; - -export const TableRow = ({ - children, - className = "", - onClick, -}: TableRowProps) => { - return ( - - {children} - - ); -}; - -export const TableHeaderCell = ({ - children, - className = "", - colSpan, -}: TableHeaderCellProps) => { - return ( - - {children} - - ); -}; - -export const TableCell = ({ - children, - className = "", - colSpan, -}: TableCellProps) => { - return ( - - {children} - - ); -}; diff --git a/apps/client/src/ui/TextInput.tsx b/apps/client/src/ui/TextInput.tsx deleted file mode 100644 index 12bbf7c..0000000 --- a/apps/client/src/ui/TextInput.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { cva, type VariantProps } from "class-variance-authority"; -import type { InputHTMLAttributes } from "react"; - -const textInputVariants = cva( - "w-full rounded bg-ctp-surface0 p-2 text-ctp-text placeholder-ctp-subtext0 outline-none transition-colors focus:ring-2 focus:ring-ctp-mauve focus:ring-offset-1", - { - variants: { - size: { - sm: "px-2 py-1 text-xs", - md: "px-3 py-2 text-sm", - lg: "px-4 py-3 text-base", - }, - state: { - default: "", - error: "border border-ctp-red focus:ring-ctp-red", - success: "border border-ctp-green focus:ring-ctp-green", - }, - }, - defaultVariants: { - size: "md", - state: "default", - }, - }, -); - -type TextInputProps = { - label: string; - error?: string | undefined; - onChange?: (value: string) => void; -} & Omit, "onChange"> & - VariantProps; - -export default function TextInput({ - label, - className = "", - size, - state, - error, - onChange, - id, - ...props -}: TextInputProps) { - const handleChange = (e: React.ChangeEvent) => { - onChange?.(e.target.value); - }; - - const inputId = id || `input-${Math.random().toString(36).substr(2, 9)}`; - - return ( -
- - - {error &&

{error}

} -
- ); -} diff --git a/apps/client/src/ui/Textarea.tsx b/apps/client/src/ui/Textarea.tsx deleted file mode 100644 index 15d70f3..0000000 --- a/apps/client/src/ui/Textarea.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { cva, type VariantProps } from "class-variance-authority"; -import type { TextareaHTMLAttributes } from "react"; - -const textareaVariants = cva( - "w-full rounded bg-ctp-surface0 border border-ctp-surface1 p-2 text-ctp-text placeholder-ctp-subtext0 outline-none transition-colors focus:ring-2 focus:ring-ctp-mauve focus:ring-offset-1 focus:border-transparent", - { - variants: { - size: { - sm: "px-2 py-1 text-xs", - md: "px-3 py-2 text-sm", - lg: "px-4 py-3 text-base", - }, - state: { - default: "", - error: "border-ctp-red focus:ring-ctp-red", - success: "border-ctp-green focus:ring-ctp-green", - }, - }, - defaultVariants: { - size: "md", - state: "default", - }, - }, -); - -type TextareaProps = { - label: string; - error?: string; - onChange?: (value: string) => void; -} & Omit, "onChange"> & - VariantProps; - -export default function Textarea({ - label, - className = "", - size, - state, - error, - onChange, - id, - ...props -}: TextareaProps) { - const handleChange = (e: React.ChangeEvent) => { - onChange?.(e.target.value); - }; - - const inputId = id || `textarea-${Math.random().toString(36).substr(2, 9)}`; - - return ( -
- -