diff --git a/CLAUDE.md b/CLAUDE.md index 8e113e8..6296254 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -39,4 +39,5 @@ This is a React/TypeScript NAO (Network of Authentic Others) community managemen - Formatting: eslint & prettier with 2-space indentation, 100 char line width - Components: Functional components with named exports - Naming: PascalCase for components/types, camelCase for variables/functions -- Comments: No comments about changes or removals, only absolutely necessary comments in any case. \ No newline at end of file +- Comments: No comments about changes or removals, only absolutely necessary comments in any case. +- YOU MUST FOLLOW ALL ESLINT RULES, NO EXCEPTIONS. \ No newline at end of file diff --git a/src/.ldo/socialquery.typings.ts b/src/.ldo/socialquery.typings.ts index 6afaffa..2e62745 100644 --- a/src/.ldo/socialquery.typings.ts +++ b/src/.ldo/socialquery.typings.ts @@ -1,4 +1,4 @@ -import { LdoJsonldContext, LdSet } from "@ldo/ldo"; +import { LdoJsonldContext } from "@ldo/ldo"; /** * ============================================================================= diff --git a/src/components/account/MyCollectionPage.tsx b/src/components/account/MyCollectionPage.tsx index c4fc96a..9af2a14 100644 --- a/src/components/account/MyCollectionPage.tsx +++ b/src/components/account/MyCollectionPage.tsx @@ -46,11 +46,8 @@ import { } from '@mui/icons-material'; import type { BookmarkedItem, Collection, CollectionFilter, CollectionStats } from '@/types/collection'; -interface MyCollectionPageProps { - // Props would come from parent component -} -const MyCollectionPage = ({}: MyCollectionPageProps) => { +const MyCollectionPage = () => { const theme = useTheme(); const [items, setItems] = useState([]); const [collections, setCollections] = useState([]); @@ -63,7 +60,7 @@ const MyCollectionPage = ({}: MyCollectionPageProps) => { const [menuAnchor, setMenuAnchor] = useState<{ [key: string]: HTMLElement | null }>({}); const [showQueryDialog, setShowQueryDialog] = useState(false); const [queryText, setQueryText] = useState(''); - const [/* stats */] = useState({ + const [/* stats */, /* setStats */] = useState({ totalItems: 0, unreadItems: 0, favoriteItems: 0, @@ -255,7 +252,7 @@ const MyCollectionPage = ({}: MyCollectionPageProps) => { // Filter by collection if (selectedCollection !== 'all') { // In a real implementation, this would filter by collection membership - filtered = filtered; // For now, show all items + // For now, show all items - no filtering needed } // Filter by category diff --git a/src/components/account/MyHomePage.tsx b/src/components/account/MyHomePage.tsx index b337156..d717972 100644 --- a/src/components/account/MyHomePage.tsx +++ b/src/components/account/MyHomePage.tsx @@ -36,11 +36,8 @@ import { } from '@mui/icons-material'; import type { UserContent, ContentFilter, ContentStats, ContentType } from '@/types/userContent'; -interface MyHomePageProps { - // Props would come from parent component -} -const MyHomePage = ({}: MyHomePageProps) => { +const MyHomePage = () => { const [content, setContent] = useState([]); const [filteredContent, setFilteredContent] = useState([]); const [filter] = useState({}); diff --git a/src/components/account/RCardPrivacySettings.tsx b/src/components/account/RCardPrivacySettings.tsx index 8010cfe..f80cc9e 100644 --- a/src/components/account/RCardPrivacySettings.tsx +++ b/src/components/account/RCardPrivacySettings.tsx @@ -40,7 +40,7 @@ const RCardPrivacySettings = ({ rCard, onUpdate }: RCardPrivacySettingsProps) => const handleSettingChange = ( category: string, field: string, - value: any + value: unknown ) => { const newSettings = { ...settings }; @@ -57,9 +57,9 @@ const RCardPrivacySettings = ({ rCard, onUpdate }: RCardPrivacySettingsProps) => } else if (category === 'general') { // Handle root level properties if (field === 'keyRecoveryBuddy' || field === 'circlesTrustedConnection') { - (newSettings as any)[field] = value; + (newSettings as Record)[field] = value; } else if (field === 'locationSharing' || field === 'locationDeletionHours') { - (newSettings as any)[field] = value; + (newSettings as Record)[field] = value; } } diff --git a/src/components/onboarding/BasicInfoStep.tsx b/src/components/onboarding/BasicInfoStep.tsx index f4cfcb2..40e041d 100644 --- a/src/components/onboarding/BasicInfoStep.tsx +++ b/src/components/onboarding/BasicInfoStep.tsx @@ -10,7 +10,7 @@ import { InputAdornment } from '@mui/material'; import { Person, Email, Phone, Business, Work, PhotoCamera } from '@mui/icons-material'; -import { useOnboarding } from '@/contexts/OnboardingContext'; +import { useOnboarding } from '@/hooks/useOnboarding'; const BasicInfoStep = () => { const { state, updateProfile } = useOnboarding(); @@ -55,7 +55,7 @@ const BasicInfoStep = () => { } break; case 'phone': - if (value && !/^\+?[\d\s\-\(\)]+$/.test(value)) { + if (value && !/^\+?[\d\s\-()]+$/.test(value)) { newErrors[field] = 'Please enter a valid phone number'; } break; diff --git a/src/components/onboarding/ConnectAccountsStep.tsx b/src/components/onboarding/ConnectAccountsStep.tsx index d92dfea..601fc90 100644 --- a/src/components/onboarding/ConnectAccountsStep.tsx +++ b/src/components/onboarding/ConnectAccountsStep.tsx @@ -27,14 +27,14 @@ import { Cloud, Security } from '@mui/icons-material'; -import { useOnboarding } from '@/contexts/OnboardingContext'; +import { useOnboarding } from '@/hooks/useOnboarding'; const ConnectAccountsStep = () => { const { state, connectAccount, disconnectAccount } = useOnboarding(); const [connectingAccount, setConnectingAccount] = useState(null); const [connectionDialog, setConnectionDialog] = useState<{ open: boolean; - account: any; + account: { id: string; type: string; isConnected: boolean; [key: string]: unknown; } | null; action: 'connect' | 'disconnect'; }>({ open: false, @@ -72,7 +72,7 @@ const ConnectAccountsStep = () => { } }; - const handleConnectionToggle = (account: any) => { + const handleConnectionToggle = (account: { id: string; type: string; isConnected: boolean; [key: string]: unknown; }) => { if (account.isConnected) { setConnectionDialog({ open: true, diff --git a/src/constants/onboarding.ts b/src/constants/onboarding.ts new file mode 100644 index 0000000..d53b5a5 --- /dev/null +++ b/src/constants/onboarding.ts @@ -0,0 +1,34 @@ +import type { OnboardingState } from '@/types/onboarding'; + +export const initialState: OnboardingState = { + currentStep: 0, + totalSteps: 2, + userProfile: {}, + connectedAccounts: [ + { + id: 'linkedin', + type: 'linkedin', + name: 'LinkedIn', + isConnected: false, + }, + { + id: 'contacts', + type: 'contacts', + name: 'Contacts', + isConnected: false, + }, + { + id: 'google', + type: 'google', + name: 'Google', + isConnected: false, + }, + { + id: 'apple', + type: 'apple', + name: 'Apple', + isConnected: false, + }, + ], + isComplete: false, +}; \ No newline at end of file diff --git a/src/contexts/OnboardingContext.tsx b/src/contexts/OnboardingContext.tsx index 9d57855..cbae7cf 100644 --- a/src/contexts/OnboardingContext.tsx +++ b/src/contexts/OnboardingContext.tsx @@ -1,39 +1,8 @@ -import { createContext, useContext, useReducer } from 'react'; +import { useReducer } from 'react'; import type { ReactNode } from 'react'; import type { OnboardingState, OnboardingContextType, UserProfile } from '@/types/onboarding'; - -const initialState: OnboardingState = { - currentStep: 0, - totalSteps: 2, - userProfile: {}, - connectedAccounts: [ - { - id: 'linkedin', - type: 'linkedin', - name: 'LinkedIn', - isConnected: false, - }, - { - id: 'contacts', - type: 'contacts', - name: 'Contacts', - isConnected: false, - }, - { - id: 'google', - type: 'google', - name: 'Google', - isConnected: false, - }, - { - id: 'apple', - type: 'apple', - name: 'Apple', - isConnected: false, - }, - ], - isComplete: false, -}; +import { OnboardingContext } from '@/contexts/OnboardingContextType'; +import { initialState } from '@/constants/onboarding'; type OnboardingAction = | { type: 'UPDATE_PROFILE'; payload: Partial } @@ -98,7 +67,6 @@ const onboardingReducer = (state: OnboardingState, action: OnboardingAction): On } }; -const OnboardingContext = createContext(undefined); export const OnboardingProvider = ({ children }: { children: ReactNode }) => { const [state, dispatch] = useReducer(onboardingReducer, initialState); @@ -144,10 +112,3 @@ export const OnboardingProvider = ({ children }: { children: ReactNode }) => { ); }; -export const useOnboarding = () => { - const context = useContext(OnboardingContext); - if (context === undefined) { - throw new Error('useOnboarding must be used within an OnboardingProvider'); - } - return context; -}; \ No newline at end of file diff --git a/src/contexts/OnboardingContextType.ts b/src/contexts/OnboardingContextType.ts new file mode 100644 index 0000000..a67d15d --- /dev/null +++ b/src/contexts/OnboardingContextType.ts @@ -0,0 +1,4 @@ +import { createContext } from 'react'; +import type { OnboardingContextType } from '@/types/onboarding'; + +export const OnboardingContext = createContext(undefined); \ No newline at end of file diff --git a/src/hooks/useOnboarding.ts b/src/hooks/useOnboarding.ts new file mode 100644 index 0000000..df94cb6 --- /dev/null +++ b/src/hooks/useOnboarding.ts @@ -0,0 +1,10 @@ +import { useContext } from 'react'; +import { OnboardingContext } from '@/contexts/OnboardingContextType'; + +export const useOnboarding = () => { + const context = useContext(OnboardingContext); + if (context === undefined) { + throw new Error('useOnboarding must be used within an OnboardingProvider'); + } + return context; +}; \ No newline at end of file diff --git a/src/lib/greencheck-api-client/index.ts b/src/lib/greencheck-api-client/index.ts index c853ebc..090175b 100644 --- a/src/lib/greencheck-api-client/index.ts +++ b/src/lib/greencheck-api-client/index.ts @@ -23,10 +23,12 @@ function getGlobalFetch(): typeof fetch { if (typeof global !== 'undefined' && (global as Record).fetch) { return ((global as Record).fetch as typeof fetch).bind(global); } - // For Node.js environments without fetch polyfill + // For Node.js environments without fetch polyfill - use dynamic import + let nodeFetch: typeof fetch; try { - const nodeFetch = require('node-fetch'); - return nodeFetch.default || nodeFetch; + // eslint-disable-next-line @typescript-eslint/no-require-imports + nodeFetch = require('node-fetch'); + return nodeFetch; } catch { throw new Error('No fetch implementation found. Please install node-fetch for Node.js environments.'); } diff --git a/src/pages/ContactListPage.tsx b/src/pages/ContactListPage.tsx index 80dc0bb..9e68432 100644 --- a/src/pages/ContactListPage.tsx +++ b/src/pages/ContactListPage.tsx @@ -105,7 +105,7 @@ const ContactListPage = () => { }, []); useEffect(() => { - let filtered = contacts.filter(contact => { + const filtered = contacts.filter(contact => { // Search filter const matchesSearch = searchQuery === '' || contact.name.toLowerCase().includes(searchQuery.toLowerCase()) || @@ -134,21 +134,24 @@ const ContactListPage = () => { case 'name': compareValue = a.name.localeCompare(b.name); break; - case 'company': + case 'company': { const aCompany = a.company || ''; const bCompany = b.company || ''; compareValue = aCompany.localeCompare(bCompany); break; - case 'naoStatus': + } + case 'naoStatus': { const statusOrder = { 'member': 0, 'invited': 1, 'not_invited': 2 }; compareValue = (statusOrder[a.naoStatus as keyof typeof statusOrder] || 3) - (statusOrder[b.naoStatus as keyof typeof statusOrder] || 3); break; - case 'groupCount': + } + case 'groupCount': { const aGroups = a.groupIds?.length || 0; const bGroups = b.groupIds?.length || 0; compareValue = aGroups - bGroups; break; + } default: compareValue = 0; } diff --git a/src/pages/GroupDetailPage.tsx b/src/pages/GroupDetailPage.tsx index 9931214..77c0249 100644 --- a/src/pages/GroupDetailPage.tsx +++ b/src/pages/GroupDetailPage.tsx @@ -261,7 +261,7 @@ const GroupDetailPage = () => { }; loadGroupData(); - }, [groupId]); + }, [groupId, searchParams, setSearchParams]); // Set initial prompt when provided useEffect(() => { @@ -674,11 +674,11 @@ const GroupDetailPage = () => { const members = getMockMembers(); // Get all unique people and topics for filters - const allPeople = ['all', ...Array.from(new Set(posts.map((p: any) => p.authorName)))]; - const allTopics = ['all', ...Array.from(new Set(posts.map((p: any) => p.topic).filter(Boolean)))]; + const allPeople = ['all', ...Array.from(new Set(posts.map((p) => p.authorName)))]; + const allTopics = ['all', ...Array.from(new Set(posts.map((p) => (p as { topic?: string }).topic).filter(Boolean)))]; // Filter posts based on selected filters - const filteredPosts = posts.filter((post: any) => { + const filteredPosts = posts.filter((post) => { const personMatch = selectedPersonFilter === 'all' || post.authorName === selectedPersonFilter; const topicMatch = selectedTopicFilter === 'all' || post.topic === selectedTopicFilter; return personMatch && topicMatch; @@ -694,7 +694,7 @@ const GroupDetailPage = () => { setExpandedPosts(newExpanded); }; - const renderPost = (post: any) => { + const renderPost = (post: GroupPost & { topic?: string; images?: string[]; isLong?: boolean }) => { const isExpanded = expandedPosts.has(post.id); const isLongPost = post.isLong; const shouldTruncate = isLongPost && !isExpanded; @@ -1276,9 +1276,9 @@ const GroupDetailPage = () => { - const renderNetworkView = (members: any[]) => { + const renderNetworkView = (members: Array<{ id: string; name: string; avatar?: string; [key: string]: unknown; }>) => { // Shared position calculation function for perfect alignment - const getNodePosition = (member: any) => { + const getNodePosition = (member: { id: string; name: string; [key: string]: unknown; }) => { const centerX = 400; // Center of 800px viewBox const centerY = 400; // Center of 800px viewBox const scale = 1.8; // Increased scale for better visibility @@ -1486,7 +1486,7 @@ const GroupDetailPage = () => { }; - const renderMapView = (members: any[], compact?: boolean) => { + const renderMapView = (members: Array<{ id: string; name: string; location?: string; [key: string]: unknown; }>, compact?: boolean) => { const visibleMembers = members.filter(m => m.location?.visible); return ( diff --git a/src/pages/GroupJoinPage.tsx b/src/pages/GroupJoinPage.tsx index f49c6f4..474ed38 100644 --- a/src/pages/GroupJoinPage.tsx +++ b/src/pages/GroupJoinPage.tsx @@ -36,7 +36,7 @@ const GroupJoinPage = () => { const [selectedProfileCard, setSelectedProfileCard] = useState(''); const [inviterName, setInviterName] = useState(''); const [isLoading, setIsLoading] = useState(true); - const [customProfileCard, setCustomProfileCard] = useState(null); + const [customProfileCard, setCustomProfileCard] = useState<{ id: string; name: string; [key: string]: unknown; } | null>(null); const navigate = useNavigate(); const [searchParams] = useSearchParams(); const theme = useTheme(); diff --git a/src/pages/SocialContractPage.tsx b/src/pages/SocialContractPage.tsx index 0b1bd88..352c8ab 100644 --- a/src/pages/SocialContractPage.tsx +++ b/src/pages/SocialContractPage.tsx @@ -97,7 +97,7 @@ const SocialContractPage = () => { }; loadGroupData(); - }, [searchParams]); + }, [searchParams, navigate]); const handleAccept = () => { // Store acceptance in session diff --git a/src/services/dataService.ts b/src/services/dataService.ts index 35b3f7c..49faee6 100644 --- a/src/services/dataService.ts +++ b/src/services/dataService.ts @@ -3,12 +3,12 @@ import type { Group } from '@/types/group'; export const dataService = { async getContacts(): Promise { - return new Promise(async (resolve) => { + return new Promise((resolve) => { setTimeout(async () => { try { const response = await fetch('/contacts.json'); const contactsData = await response.json(); - const contacts = contactsData.map((contact: any) => { + const contacts = contactsData.map((contact: Contact & { createdAt: string; updatedAt: string; joinedAt?: string; invitedAt?: string; }) => { const processedContact = { ...contact, createdAt: new Date(contact.createdAt), @@ -35,12 +35,12 @@ export const dataService = { }, async getContact(id: string): Promise { - return new Promise(async (resolve) => { + return new Promise((resolve) => { setTimeout(async () => { try { const response = await fetch('/contacts.json'); const contactsData = await response.json(); - const contact = contactsData.find((c: any) => c.id === id); + const contact = contactsData.find((c: Contact) => c.id === id); if (contact) { const processedContact = { ...contact, @@ -69,7 +69,7 @@ export const dataService = { }, async getImportSources(): Promise { - return new Promise(async (resolve) => { + return new Promise((resolve) => { setTimeout(async () => { try { const response = await fetch('/import-sources.json'); @@ -84,14 +84,14 @@ export const dataService = { }, async importFromSource(sourceId: string): Promise { - return new Promise(async (resolve) => { + return new Promise((resolve) => { setTimeout(async () => { try { const response = await fetch('/contacts.json'); const contactsData = await response.json(); const filteredContacts = contactsData - .filter((contact: any) => contact.source === sourceId) - .map((contact: any) => ({ + .filter((contact: Contact) => contact.source === sourceId) + .map((contact: Contact) => ({ ...contact, createdAt: new Date(contact.createdAt), updatedAt: new Date(contact.updatedAt) @@ -106,14 +106,14 @@ export const dataService = { }, async getGroups(): Promise { - return new Promise(async (resolve) => { + return new Promise((resolve) => { setTimeout(async () => { try { const response = await fetch('/groups.json'); const groupsData = await response.json(); - const groups = groupsData.map((group: any) => { + const groups = groupsData.map((group: Group & { createdAt: string; updatedAt: string; latestPostAt?: string; }) => { const processedGroup = { - ...group, + ...(group as unknown as Group), createdAt: new Date(group.createdAt), updatedAt: new Date(group.updatedAt) }; @@ -135,15 +135,15 @@ export const dataService = { }, async getGroup(id: string): Promise { - return new Promise(async (resolve) => { + return new Promise((resolve) => { setTimeout(async () => { try { const response = await fetch('/groups.json'); const groupsData = await response.json(); - const group = groupsData.find((g: any) => g.id === id); + const group = groupsData.find((g: Group) => g.id === id); if (group) { const processedGroup = { - ...group, + ...(group as unknown as Group), createdAt: new Date(group.createdAt), updatedAt: new Date(group.updatedAt) }; @@ -166,23 +166,23 @@ export const dataService = { }, async getGroupsForUser(userId: string): Promise { - return new Promise(async (resolve) => { + return new Promise((resolve) => { setTimeout(async () => { try { const response = await fetch('/groups.json'); const groupsData = await response.json(); const userGroups = groupsData - .filter((group: any) => group.memberIds.includes(userId)) - .map((group: any) => { - const processedGroup = { - ...group, - createdAt: new Date(group.createdAt), - updatedAt: new Date(group.updatedAt) + .filter((group: Record) => (group.memberIds as string[]).includes(userId)) + .map((group: Record) => { + const processedGroup: Group = { + ...(group as unknown as Group), + createdAt: new Date(group.createdAt as string), + updatedAt: new Date(group.updatedAt as string) }; // Convert optional date fields if they exist if (group.latestPostAt) { - processedGroup.latestPostAt = new Date(group.latestPostAt); + processedGroup.latestPostAt = new Date(group.latestPostAt as string); } return processedGroup; diff --git a/src/services/notificationService.ts b/src/services/notificationService.ts index 705945e..286ce79 100644 --- a/src/services/notificationService.ts +++ b/src/services/notificationService.ts @@ -196,7 +196,7 @@ export class NotificationService { } // Accept a vouch - async acceptVouch(notificationId: string, _vouchId: string): Promise { + async acceptVouch(notificationId: string): Promise { await new Promise(resolve => setTimeout(resolve, 400)); const notification = this.notifications.find(n => n.id === notificationId); if (notification) { @@ -207,7 +207,7 @@ export class NotificationService { } // Reject a vouch - async rejectVouch(notificationId: string, _vouchId: string): Promise { + async rejectVouch(notificationId: string): Promise { await new Promise(resolve => setTimeout(resolve, 400)); const notification = this.notifications.find(n => n.id === notificationId); if (notification) { @@ -218,7 +218,7 @@ export class NotificationService { } // Accept praise - async acceptPraise(notificationId: string, _praiseId: string): Promise { + async acceptPraise(notificationId: string): Promise { await new Promise(resolve => setTimeout(resolve, 400)); const notification = this.notifications.find(n => n.id === notificationId); if (notification) { @@ -229,7 +229,7 @@ export class NotificationService { } // Reject praise - async rejectPraise(notificationId: string, _praiseId: string): Promise { + async rejectPraise(notificationId: string): Promise { await new Promise(resolve => setTimeout(resolve, 400)); const notification = this.notifications.find(n => n.id === notificationId); if (notification) { diff --git a/src/types/nextgraph.ts b/src/types/nextgraph.ts index 1b75146..b123e0d 100644 --- a/src/types/nextgraph.ts +++ b/src/types/nextgraph.ts @@ -1,12 +1,12 @@ export interface NextGraphSession { - ng?: any; + ng?: unknown; privateStoreId?: string; - [key: string]: any; + [key: string]: unknown; } export interface NextGraphAuth { session?: NextGraphSession; login?: () => void; logout?: () => void; - [key: string]: any; + [key: string]: unknown; } \ No newline at end of file diff --git a/src/types/notification.ts b/src/types/notification.ts index 6797470..ac2f5ee 100644 --- a/src/types/notification.ts +++ b/src/types/notification.ts @@ -10,7 +10,7 @@ export interface ProfileCard { } // Legacy alias for backwards compatibility -export interface RCard extends ProfileCard {} +export type RCard = ProfileCard; export interface Vouch { id: string; @@ -130,7 +130,7 @@ export interface ProfileCardWithPrivacy extends ProfileCard { } // Legacy alias for backwards compatibility -export interface RCardWithPrivacy extends ProfileCardWithPrivacy {} +export type RCardWithPrivacy = ProfileCardWithPrivacy; export interface ContactPrivacyOverride { contactId: string;