diff --git a/package-lock.json b/package-lock.json index 9695b0d5..567b5d17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,6 +38,7 @@ "@radix-ui/react-toggle": "^1.1.9", "@radix-ui/react-toggle-group": "^1.1.10", "@radix-ui/react-tooltip": "^1.2.7", + "@sentry/react": "^10.67.0", "@supabase/supabase-js": "^2.102.1", "@tanstack/react-query": "^5.83.0", "@tanstack/react-virtual": "^3.14.2", @@ -2997,6 +2998,112 @@ "win32" ] }, + "node_modules/@sentry/browser": { + "version": "10.67.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.67.0.tgz", + "integrity": "sha512-/ZhsAvte4rYhg0A0RtSFFgAgXhyMOfQIeOAfMfptN+X6IVSYOfkA9jtrP+Ej4+6vlaUFWRir1HweF56y63dEEA==", + "license": "MIT", + "dependencies": { + "@sentry/browser-utils": "10.67.0", + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.67.0", + "@sentry/feedback": "10.67.0", + "@sentry/replay": "10.67.0", + "@sentry/replay-canvas": "10.67.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/browser-utils": { + "version": "10.67.0", + "resolved": "https://registry.npmjs.org/@sentry/browser-utils/-/browser-utils-10.67.0.tgz", + "integrity": "sha512-HUzaf0xAnPAB+OHBkD7N1Py+CTbD5InHulQ/pdhX4JctWtxuwD8odMD1LzdPnW8J6gVHlDVvcVBR8mXMZYSLSw==", + "license": "MIT", + "dependencies": { + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.67.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/conventions": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@sentry/conventions/-/conventions-0.16.0.tgz", + "integrity": "sha512-fO9PLmHdVURcSPUpWCItWAtgKiMwGdJHbovoSEyLplX5sxs2ugvI4CBPTrkkgqhObnZOD0CnWBKDzSVQYBKEyQ==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@sentry/core": { + "version": "10.67.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.67.0.tgz", + "integrity": "sha512-b6U3pJ8AUvN9aouq0vl+VZI8KT8RslBsfGMFuNwRr313zOmdmFJBZqTiUw9VGgJ2jGKxLO9alm9rlxBfX4hf+w==", + "license": "MIT", + "dependencies": { + "@sentry/conventions": "^0.16.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/feedback": { + "version": "10.67.0", + "resolved": "https://registry.npmjs.org/@sentry/feedback/-/feedback-10.67.0.tgz", + "integrity": "sha512-I4ML2/SF3enwikb6ZSoRiqolQrx0zSzTSnUgwCmugICF/jpHW0th1pCray9R+t1Zzibw/Dpj4t/DNXaSDRa2MA==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.67.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/react": { + "version": "10.67.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-10.67.0.tgz", + "integrity": "sha512-fS0DplcP9eMxBIRurPC/uxa4NrFK+l9ZsnvQo7wZvNutc7DpTAH0hgFt6laVNCe57s1pFo+OZuKsYBA6JDvH4Q==", + "license": "MIT", + "dependencies": { + "@sentry/browser": "10.67.0", + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.67.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.14.0 || 17.x || 18.x || 19.x" + } + }, + "node_modules/@sentry/replay": { + "version": "10.67.0", + "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-10.67.0.tgz", + "integrity": "sha512-nkEUgPCR82EcyJkCf3XCE9H0R5KisCqyCAaSGxe7NpAoQbvASHx4MUNgXVAn+D0M494gvPZh6lFH7JgzqTcSqQ==", + "license": "MIT", + "dependencies": { + "@sentry/browser-utils": "10.67.0", + "@sentry/core": "10.67.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/replay-canvas": { + "version": "10.67.0", + "resolved": "https://registry.npmjs.org/@sentry/replay-canvas/-/replay-canvas-10.67.0.tgz", + "integrity": "sha512-neNA4T6MFtZzMdKYetiR+LZd9BNSd0q2szMn0wk+A15PqHE/IN7a34V6JZc9rCtmzB0wldh0eWGOBb49MSNKjA==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.67.0", + "@sentry/replay": "10.67.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", diff --git a/package.json b/package.json index 303aa5dc..677dd5ba 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@radix-ui/react-toggle": "^1.1.9", "@radix-ui/react-toggle-group": "^1.1.10", "@radix-ui/react-tooltip": "^1.2.7", + "@sentry/react": "^10.67.0", "@supabase/supabase-js": "^2.102.1", "@tanstack/react-query": "^5.83.0", "@tanstack/react-virtual": "^3.14.2", diff --git a/src/App.tsx b/src/App.tsx index f2c5022c..23ac4093 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -105,7 +105,8 @@ function AppContent() { <> - }> + + } /> - + + {user && ( <> diff --git a/src/components/AvatarUpload.tsx b/src/components/AvatarUpload.tsx index 1f17d11c..b6fcfd39 100644 --- a/src/components/AvatarUpload.tsx +++ b/src/components/AvatarUpload.tsx @@ -2,7 +2,6 @@ import React, { useRef, useState } from "react"; import { Camera, Loader2 } from "lucide-react"; import { supabase } from "@/integrations/supabase/client"; import { API_BASE_URL } from "@/config/api"; - type AvatarUploadProps = { currentAvatarUrl: string; onUploadSuccess: (url: string) => void; diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx index 8a869663..ebdb696c 100644 --- a/src/components/ErrorBoundary.tsx +++ b/src/components/ErrorBoundary.tsx @@ -1,4 +1,5 @@ import React from "react"; +import * as Sentry from "@sentry/react"; import { AlertTriangle, RefreshCw, Home } from "lucide-react"; interface Props { @@ -20,8 +21,12 @@ class ErrorBoundary extends React.Component { } componentDidCatch(error: Error, info: React.ErrorInfo) { - // TODO: hook this up to an error tracking service later console.error("ErrorBoundary caught an error:", error, info.componentStack); + Sentry.captureException(error, { + extra: { + componentStack: info.componentStack, + }, + }); } resetError = () => { diff --git a/src/components/FloatingAI.tsx b/src/components/FloatingAI.tsx index a8de44a2..77b3a483 100644 --- a/src/components/FloatingAI.tsx +++ b/src/components/FloatingAI.tsx @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ + import { useState } from "react"; import { Bot, Send, X, User } from "lucide-react"; import { API_BASE_URL } from "@/config/api"; diff --git a/src/components/NotificationsDropdown.tsx b/src/components/NotificationsDropdown.tsx index 6505cb30..103f4662 100644 --- a/src/components/NotificationsDropdown.tsx +++ b/src/components/NotificationsDropdown.tsx @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ + import { useState, useEffect, useRef } from "react"; import { Bell } from "lucide-react"; import { supabase } from "@/integrations/supabase/client"; diff --git a/src/components/Room/ChatBox.tsx b/src/components/Room/ChatBox.tsx index 6f8f6219..7f393220 100644 --- a/src/components/Room/ChatBox.tsx +++ b/src/components/Room/ChatBox.tsx @@ -9,8 +9,12 @@ const MarkdownRenderer = React.lazy(() => ); interface ChatBoxProps { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - messages: any[]; + messages: { + id: string; + profile_id: string; + content: string; + profiles?: { name: string | null } | null; + }[]; user: User | null; onSendMessage: (msg: string) => Promise; } diff --git a/src/components/Room/InviteMenu.tsx b/src/components/Room/InviteMenu.tsx index bc0ab93a..d3ee3f94 100644 --- a/src/components/Room/InviteMenu.tsx +++ b/src/components/Room/InviteMenu.tsx @@ -14,8 +14,7 @@ export const InviteMenu = React.memo(function InviteMenu({ roomId }: InviteMenuP const handleInvite = async () => { if (!inviteEmail.trim()) return; setIsInviting(true); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const { error } = await (supabase.rpc as any)("invite_to_study_room", { + const { error } = await supabase.rpc("invite_to_study_room", { p_room_id: roomId, p_user_email: inviteEmail, }); diff --git a/src/components/Sparkles.tsx b/src/components/Sparkles.tsx index d90eb227..f3b5068e 100644 --- a/src/components/Sparkles.tsx +++ b/src/components/Sparkles.tsx @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ + import React, { useEffect, useRef } from 'react'; const Sparkles: React.FC = () => { diff --git a/src/components/StudyRooms.tsx b/src/components/StudyRooms.tsx index 40171c28..10368ba3 100644 --- a/src/components/StudyRooms.tsx +++ b/src/components/StudyRooms.tsx @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ + import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { supabase } from '@/integrations/supabase/client'; diff --git a/src/components/Whiteboard/Canvas.tsx b/src/components/Whiteboard/Canvas.tsx index 677cde97..db87847f 100644 --- a/src/components/Whiteboard/Canvas.tsx +++ b/src/components/Whiteboard/Canvas.tsx @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ + import { useCallback, useEffect, useRef, useState } from "react"; import { supabase } from "@/integrations/supabase/client"; import { useAuth } from "@/contexts/useAuth"; diff --git a/src/components/dashboard/RecentActivity.tsx b/src/components/dashboard/RecentActivity.tsx index 675a56db..a0f5b005 100644 --- a/src/components/dashboard/RecentActivity.tsx +++ b/src/components/dashboard/RecentActivity.tsx @@ -49,7 +49,7 @@ export default function RecentActivity() { supabase .from("resources") .select("id, title, created_at") - .eq("user_id", user.id) + .eq("uploaded_by", user.id) .order("created_at", { ascending: false }) .limit(3), supabase diff --git a/src/components/landing/Testimonials.tsx b/src/components/landing/Testimonials.tsx index 1dc4a126..7e8d920e 100644 --- a/src/components/landing/Testimonials.tsx +++ b/src/components/landing/Testimonials.tsx @@ -50,22 +50,7 @@ function mapDbRowToTestimonial(row: { }; } -export function Testimonials() { - const scrollRef = useRef(null); - const testimonialAutoScrollRef = useRef(null); - const testimonialPausedRef = useRef(false); - const { user } = useAuth(); - - const [name, setName] = useState(""); - const [rating, setRating] = useState(0); - const [review, setReview] = useState(""); - const [submitted, setSubmitted] = useState(false); - const [isSubmitting, setIsSubmitting] = useState(false); - const [liveTestimonials, setLiveTestimonials] = useState([]); - - // Seeded fallback content — always shown so the carousel never looks empty - // while real submissions are still trickling in. - const seedTestimonials: Testimonial[] = [ +const seedTestimonials: Testimonial[] = [ { text: "PeerLearn helped me crack my first internship interview.", name: "Aisha Khan", @@ -128,6 +113,23 @@ export function Testimonials() { }, ]; +export function Testimonials() { + const scrollRef = useRef(null); + const testimonialAutoScrollRef = useRef(null); + const testimonialPausedRef = useRef(false); + const { user } = useAuth(); + + const [name, setName] = useState(""); + const [rating, setRating] = useState(0); + const [review, setReview] = useState(""); + const [submitted, setSubmitted] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); + const [liveTestimonials, setLiveTestimonials] = useState([]); + + // Seeded fallback content — always shown so the carousel never looks empty + // while real submissions are still trickling in. + + const fetchTestimonials = useCallback(async () => { const { data, error } = await supabase .from("testimonials") diff --git a/src/components/markdown/MarkdownRenderer.tsx b/src/components/markdown/MarkdownRenderer.tsx index b9b4ac8b..7203e0fc 100644 --- a/src/components/markdown/MarkdownRenderer.tsx +++ b/src/components/markdown/MarkdownRenderer.tsx @@ -1 +1,2 @@ +/* eslint-disable react-refresh/only-export-components */ export { MarkdownRenderer, default } from "@/components/MarkdownRenderer"; \ No newline at end of file diff --git a/src/components/mentor/MentorForm.tsx b/src/components/mentor/MentorForm.tsx index 6f94c175..a29d6b6e 100644 --- a/src/components/mentor/MentorForm.tsx +++ b/src/components/mentor/MentorForm.tsx @@ -1,3 +1,4 @@ + import { useState, useEffect } from "react"; import { motion } from "framer-motion"; import { CheckCircle2, Loader2, Plus, X } from "lucide-react"; diff --git a/src/components/theme-provider.tsx b/src/components/theme-provider.tsx index 33c1494b..68696cd4 100644 --- a/src/components/theme-provider.tsx +++ b/src/components/theme-provider.tsx @@ -1 +1,2 @@ +/* eslint-disable react-refresh/only-export-components */ export { ThemeProvider, useTheme } from "@/contexts/ThemeContext"; \ No newline at end of file diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx index 1ffd27e3..67947863 100644 --- a/src/components/ui/sonner.tsx +++ b/src/components/ui/sonner.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-refresh/only-export-components */ import { Toaster as Sonner, toast } from "sonner"; type ToasterProps = React.ComponentProps; diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx index 5fd19089..703ef7ed 100644 --- a/src/components/ui/textarea.tsx +++ b/src/components/ui/textarea.tsx @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-object-type, no-unsafe-finally, @typescript-eslint/no-unused-expressions, @typescript-eslint/ban-ts-comment, @typescript-eslint/no-require-imports */ + import * as React from "react"; import { cn } from "@/lib/utils"; diff --git a/src/features/notifications/pushNotifications.ts b/src/features/notifications/pushNotifications.ts index ccf968f7..54859249 100644 --- a/src/features/notifications/pushNotifications.ts +++ b/src/features/notifications/pushNotifications.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ + import { supabase } from "@/integrations/supabase/client"; import { env } from "@/env"; import { sanitizeNotificationActionUrl } from "./actionUrl"; diff --git a/src/features/notifications/useNotifications.ts b/src/features/notifications/useNotifications.ts index 8820f1e7..872d010f 100644 --- a/src/features/notifications/useNotifications.ts +++ b/src/features/notifications/useNotifications.ts @@ -1,5 +1,5 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { useCallback, useEffect, useState } from "react"; + +import { useCallback, useEffect, useMemo, useState } from "react"; import { supabase } from "@/integrations/supabase/client"; import type { Notification } from "./types"; import { showBrowserNotification } from "./pushNotifications"; diff --git a/src/hooks/useAwardXP.ts b/src/hooks/useAwardXP.ts index b26eecf8..6427a9c9 100644 --- a/src/hooks/useAwardXP.ts +++ b/src/hooks/useAwardXP.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ + import { useMutation, useQueryClient } from "@tanstack/react-query"; import { supabase } from "@/integrations/supabase/client"; import { getXPForActivity } from "@/lib/gamification"; diff --git a/src/hooks/useRoomChat.ts b/src/hooks/useRoomChat.ts index 062bc1af..8a9a7e78 100644 --- a/src/hooks/useRoomChat.ts +++ b/src/hooks/useRoomChat.ts @@ -4,13 +4,13 @@ import { supabase } from "@/integrations/supabase/client"; import { User } from "@supabase/supabase-js"; export function useRoomChat(id: string | undefined, user: User | null) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any + const [messages, setMessages] = useState([]); const fetchMessages = useCallback(async () => { if (!id) return; const { data, error } = await supabase - // eslint-disable-next-line @typescript-eslint/no-explicit-any + .from('study_room_messages' as any) .select('*, profiles(name, avatar_url)') .eq('room_id', id) @@ -30,7 +30,7 @@ export function useRoomChat(id: string | undefined, user: User | null) { const handleSendMessage = useCallback(async (newMessage: string) => { if (!newMessage.trim() || !user || !id) return false; - // eslint-disable-next-line @typescript-eslint/no-explicit-any + const { error } = await supabase.from('study_room_messages' as any).insert([ { room_id: id, profile_id: user.id, content: newMessage } ]); diff --git a/src/hooks/useRoomDetails.ts b/src/hooks/useRoomDetails.ts index ad3f7efa..11cae018 100644 --- a/src/hooks/useRoomDetails.ts +++ b/src/hooks/useRoomDetails.ts @@ -5,7 +5,7 @@ import { supabase } from "@/integrations/supabase/client"; import { User } from "@supabase/supabase-js"; export function useRoomDetails(id: string | undefined, user: User | null) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any + const [room, setRoom] = useState(null); const navigate = useNavigate(); @@ -13,7 +13,7 @@ export function useRoomDetails(id: string | undefined, user: User | null) { if (!id) return; const fetchRoomDetails = async () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any + const { data, error } = await supabase.from('study_rooms' as any).select('*').eq('id', id).single(); if (error) { console.error("Error fetching room:", error); diff --git a/src/hooks/useRoomPresence.ts b/src/hooks/useRoomPresence.ts index 68fc4d66..320fc95c 100644 --- a/src/hooks/useRoomPresence.ts +++ b/src/hooks/useRoomPresence.ts @@ -3,18 +3,18 @@ import { supabase } from "@/integrations/supabase/client"; import { User } from "@supabase/supabase-js"; export function useRoomPresence(id: string | undefined, user: User | null, fetchMessages: () => void, setActivities: React.Dispatch>) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any + const [participants, setParticipants] = useState([]); useEffect(() => { if (!id || !user) return; - // eslint-disable-next-line @typescript-eslint/no-explicit-any + let roomChannel: any; let cancelled = false; const initializeChat = async () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any + const { data } = await supabase.from('profiles' as any).select('name').eq('id', user.id).single() as any; // The effect may have been cleaned up (user left/switched rooms) while @@ -31,7 +31,7 @@ export function useRoomPresence(id: string | undefined, user: User | null, fetch roomChannel .on('presence', { event: 'sync' }, () => { const newState = roomChannel.presenceState(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any + const onlineUsers = Object.values(newState).map((p: any) => p[0]); setParticipants(onlineUsers); diff --git a/src/hooks/useSkillEndorsements.test.ts b/src/hooks/useSkillEndorsements.test.ts index 73705225..ede30e2b 100644 --- a/src/hooks/useSkillEndorsements.test.ts +++ b/src/hooks/useSkillEndorsements.test.ts @@ -9,7 +9,9 @@ vi.mock("@/integrations/supabase/client", () => ({ supabase: { auth: { getUser: vi.fn(), - onAuthStateChange: vi.fn(() => ({ data: { subscription: { unsubscribe: vi.fn() } } })), + onAuthStateChange: vi.fn(() => ({ + data: { subscription: { unsubscribe: vi.fn() } }, + })), }, from: vi.fn(), }, diff --git a/src/hooks/useSkillEndorsements.ts b/src/hooks/useSkillEndorsements.ts index af8f000b..321f24d7 100644 --- a/src/hooks/useSkillEndorsements.ts +++ b/src/hooks/useSkillEndorsements.ts @@ -38,22 +38,33 @@ export function useSkillEndorsements({ useEffect(() => { let mounted = true; - // Initial load - supabase.auth.getUser().then(({ data }) => { - if (!mounted) return; - setCurrentUserId(data.user?.id ?? null); - setAuthReady(true); - }).catch(console.error); + supabase.auth + .getUser() + .then(({ data }) => { + if (!mounted) return; + setCurrentUserId(data.user?.id ?? null); + setAuthReady(true); + }) + .catch((err) => { + console.error("[useSkillEndorsements] getUser error:", err); + if (mounted) setAuthReady(true); + }); + + // Guard for environments/mocks where this API is missing + const authApi = supabase.auth as { + onAuthStateChange?: (cb: (_event: string, session: any) => void) => { + data?: { subscription?: { unsubscribe: () => void } }; + }; + }; - // Subscribe to changes - const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => { - if (mounted) setCurrentUserId(session?.user?.id ?? null); + const sub = authApi.onAuthStateChange?.((_event, session) => { + if (!mounted) return; + setCurrentUserId(session?.user?.id ?? null); }); - // Cleanup subscription return () => { mounted = false; - subscription.unsubscribe(); + sub?.data?.subscription?.unsubscribe?.(); }; }, []); diff --git a/src/integrations/supabase/types.ts b/src/integrations/supabase/types.ts index a2c7b37a..183568f2 100644 --- a/src/integrations/supabase/types.ts +++ b/src/integrations/supabase/types.ts @@ -6,4 +6,890 @@ export type Json = | { [key: string]: Json | undefined } | Json[] -export type Database = any; +export type Database = { + // Allows to automatically instantiate createClient with right options + // instead of createClient(URL, KEY) + __InternalSupabase: { + PostgrestVersion: "14.5" + } + public: { + Tables: { + mentorship_paths: { + Row: { + id: string + mentor_id: string + mentee_id: string + goal: string + status: string + created_at: string + updated_at: string + } + Insert: { + id?: string + mentor_id: string + mentee_id: string + goal: string + status?: string + created_at?: string + updated_at?: string + } + Update: { + id?: string + mentor_id?: string + mentee_id?: string + goal?: string + status?: string + created_at?: string + updated_at?: string + } + Relationships: [ + { + foreignKeyName: "mentorship_paths_mentee_id_fkey" + columns: ["mentee_id"] + isOneToOne: false + referencedRelation: "profiles" + referencedColumns: ["id"] + }, + { + foreignKeyName: "mentorship_paths_mentor_id_fkey" + columns: ["mentor_id"] + isOneToOne: false + referencedRelation: "profiles" + referencedColumns: ["id"] + } + ] + } + mentorship_milestones: { + Row: { + id: string + path_id: string + title: string + description: string | null + is_completed: boolean + due_date: string | null + created_at: string + updated_at: string + } + Insert: { + id?: string + path_id: string + title: string + description?: string | null + is_completed?: boolean + due_date?: string | null + created_at?: string + updated_at?: string + } + Update: { + id?: string + path_id?: string + title?: string + description?: string | null + is_completed?: boolean + due_date?: string | null + created_at?: string + updated_at?: string + } + Relationships: [ + { + foreignKeyName: "mentorship_milestones_path_id_fkey" + columns: ["path_id"] + isOneToOne: false + referencedRelation: "mentorship_paths" + referencedColumns: ["id"] + } + ] + } + peer_submissions: { + Row: { + id: string + user_id: string | null + title: string + description: string | null + content_url: string | null + content: string | null + is_anonymous: boolean + status: string + created_at: string + } + Insert: { + id?: string + user_id?: string | null + title: string + description?: string | null + content_url?: string | null + content?: string | null + is_anonymous?: boolean + status?: string + created_at?: string + } + Update: { + id?: string + user_id?: string | null + title?: string + description?: string | null + content_url?: string | null + content?: string | null + is_anonymous?: boolean + status?: string + created_at?: string + } + Relationships: [ + { + foreignKeyName: "peer_submissions_user_id_fkey" + columns: ["user_id"] + isOneToOne: false + referencedRelation: "profiles" + referencedColumns: ["id"] + } + ] + } + peer_reviews: { + Row: { + id: string + submission_id: string + reviewer_id: string + feedback: string + rating: number | null + created_at: string + } + Insert: { + id?: string + submission_id: string + reviewer_id: string + feedback: string + rating?: number | null + created_at?: string + } + Update: { + id?: string + submission_id?: string + reviewer_id?: string + feedback?: string + rating?: number | null + created_at?: string + } + Relationships: [ + { + foreignKeyName: "peer_reviews_submission_id_fkey" + columns: ["submission_id"] + isOneToOne: false + referencedRelation: "peer_submissions" + referencedColumns: ["id"] + }, + { + foreignKeyName: "peer_reviews_reviewer_id_fkey" + columns: ["reviewer_id"] + isOneToOne: false + referencedRelation: "profiles" + referencedColumns: ["id"] + } + ] + } + + chat_messages: { + Row: { + created_at: string + id: number + } + Insert: { + created_at?: string + id?: number + } + Update: { + created_at?: string + id?: number + } + Relationships: [] + } + messages: { + Row: { + content: string | null + created_at: string | null + id: string + message: string | null + read_at: string | null + receiver_id: string | null + sender_id: string | null + text: string | null + } + Insert: { + content?: string | null + created_at?: string | null + id?: string + message?: string | null + read_at?: string | null + receiver_id?: string | null + sender_id?: string | null + text?: string | null + } + Update: { + content?: string | null + created_at?: string | null + id?: string + message?: string | null + read_at?: string | null + receiver_id?: string | null + sender_id?: string | null + text?: string | null + } + Relationships: [] + } + leaderboard: { + Row: { + id: string + user_id: string + username: string + avatar_url: string | null + xp: number + streak: number + sessions_joined: number + badges: string[] + updated_at: string + } + Insert: { + id?: string + user_id: string + username: string + avatar_url?: string | null + xp?: number + streak?: number + sessions_joined?: number + badges?: string[] + updated_at?: string + } + Update: { + id?: string + user_id?: string + username?: string + avatar_url?: string | null + xp?: number + streak?: number + sessions_joined?: number + badges?: string[] + updated_at?: string + } + Relationships: [ + { + foreignKeyName: "leaderboard_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "profiles" + referencedColumns: ["id"] + } + ] + } + profiles: { + Row: { + avatar_url: string | null + bio: string | null + created_at: string | null + email: string | null + id: string + last_seen: string | null + name: string | null + skills: string[] | null + is_mentor: boolean + is_learner: boolean + points: number | null + sessions_completed: number | null + rating: number | null + badges: string[] | null + interests: string[] | null + teach_subjects: string[] | null + learn_subjects: string[] | null + updated_at: string | null + streak: number + last_active: string | null + restoration_used_today: boolean + restoration_date: string | null + is_in_focus_mode: boolean | null + focus_time_this_week: number | null + learning_style: string | null + availability: string | null + preferred_language: string | null + timezone: string | null + } + Insert: { + avatar_url?: string | null + bio?: string | null + created_at?: string | null + email?: string | null + id: string + last_seen?: string | null + name?: string | null + skills?: string[] | null + is_mentor?: boolean + is_learner?: boolean + points?: number | null + sessions_completed?: number | null + rating?: number | null + badges?: string[] | null + interests?: string[] | null + teach_subjects?: string[] | null + learn_subjects?: string[] | null + updated_at?: string | null + streak?: number + last_active?: string | null + restoration_used_today?: boolean + restoration_date?: string | null + learning_style?: string | null + availability?: string | null + preferred_language?: string | null + timezone?: string | null + } + Update: { + avatar_url?: string | null + bio?: string | null + created_at?: string | null + email?: string | null + id?: string + last_seen?: string | null + name?: string | null + skills?: string[] | null + is_mentor?: boolean + is_learner?: boolean + points?: number | null + sessions_completed?: number | null + rating?: number | null + badges?: string[] | null + interests?: string[] | null + teach_subjects?: string[] | null + learn_subjects?: string[] | null + updated_at?: string | null + streak?: number + last_active?: string | null + restoration_used_today?: boolean + restoration_date?: string | null + learning_style?: string | null + availability?: string | null + preferred_language?: string | null + timezone?: string | null + } + Relationships: [] + } + resources: { + Row: { + id: string + title: string + description: string | null + file_url: string + file_size: number | null + tags: string[] | null + file_type: string + uploaded_by: string + created_at: string + } + Insert: { + id?: string + title: string + description?: string | null + file_url: string + file_size?: number | null + tags?: string[] | null + file_type: string + uploaded_by: string + created_at?: string + } + Update: { + id?: string + title?: string + description?: string | null + file_url?: string + file_size?: number | null + tags?: string[] | null + file_type?: string + uploaded_by?: string + created_at?: string + } + Relationships: [] + } + resource_votes: { + Row: { + id: string + resource_id: string + user_id: string + vote_type: number + created_at: string + } + Insert: { + id?: string + resource_id: string + user_id: string + vote_type: number + created_at?: string + } + Update: { + id?: string + resource_id?: string + user_id?: string + vote_type?: number + created_at?: string + } + Relationships: [] + } + saved_resources: { + Row: { + id: string + resource_id: string + user_id: string + created_at: string + } + Insert: { + id?: string + resource_id: string + user_id: string + created_at?: string + } + Update: { + id?: string + resource_id?: string + user_id?: string + created_at?: string + } + Relationships: [] + } + skill_endorsements: { + Row: { + id: string + skill: string + endorsed_user_id: string + endorser_id: string + created_at: string + } + Insert: { + id?: string + skill: string + endorsed_user_id: string + endorser_id: string + created_at?: string + } + Update: { + id?: string + skill?: string + endorsed_user_id?: string + endorser_id?: string + created_at?: string + } + Relationships: [] + } + testimonials: { + Row: { + id: string + user_id: string + name: string | null + rating: number | null + review: string + status: string + created_at: string + } + Insert: { + id?: string + user_id: string + name?: string | null + rating?: number | null + review: string + status?: string + created_at?: string + } + Update: { + id?: string + user_id?: string + name?: string | null + rating?: number | null + review?: string + status?: string + created_at?: string + } + Relationships: [ + { + foreignKeyName: "testimonials_user_id_fkey" + columns: ["user_id"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] + } + ] + } + study_rooms: { + Row: { + id: string + topic: string + created_by: string | null + created_at: string + is_private: boolean + } + Insert: { + id?: string + topic: string + created_by?: string | null + created_at?: string + is_private?: boolean + } + Update: { + id?: string + topic?: string + created_by?: string | null + created_at?: string + is_private?: boolean + } + Relationships: [ + { + foreignKeyName: "study_rooms_created_by_fkey" + columns: ["created_by"] + isOneToOne: false + referencedRelation: "profiles" + referencedColumns: ["id"] + } + ] + } + study_room_messages: { + Row: { + id: string + room_id: string | null + profile_id: string | null + content: string + created_at: string + } + Insert: { + id?: string + room_id?: string | null + profile_id?: string | null + content: string + created_at?: string + } + Update: { + id?: string + room_id?: string | null + profile_id?: string | null + content?: string + created_at?: string + } + Relationships: [ + { + foreignKeyName: "study_room_messages_profile_id_fkey" + columns: ["profile_id"] + isOneToOne: false + referencedRelation: "profiles" + referencedColumns: ["id"] + }, + { + foreignKeyName: "study_room_messages_room_id_fkey" + columns: ["room_id"] + isOneToOne: false + referencedRelation: "study_rooms" + referencedColumns: ["id"] + } + ] + } + study_room_participants: { + Row: { + room_id: string + profile_id: string + joined_at: string + } + Insert: { + room_id: string + profile_id: string + joined_at?: string + } + Update: { + room_id?: string + profile_id?: string + joined_at?: string + } + Relationships: [ + { + foreignKeyName: "study_room_participants_profile_id_fkey" + columns: ["profile_id"] + isOneToOne: false + referencedRelation: "profiles" + referencedColumns: ["id"] + }, + { + foreignKeyName: "study_room_participants_room_id_fkey" + columns: ["room_id"] + isOneToOne: false + referencedRelation: "study_rooms" + referencedColumns: ["id"] + } + ] + } + sessions: { + Row: { + created_at: string + description: string | null + id: number + scheduled_at: string | null + duration_minutes: number + status: string + student_id: string | null + mentor_id: string | null + seat_limit: number | null + participants: number + title: string | null + tags: string[] | null + } + Insert: { + created_at?: string + description?: string | null + id?: number + scheduled_at?: string | null + duration_minutes?: number + status?: string + student_id?: string | null + mentor_id?: string | null + seat_limit?: number | null + participants?: number + title?: string | null + tags?: string[] | null + } + Update: { + created_at?: string + description?: string | null + id?: number + scheduled_at?: string | null + duration_minutes?: number + status?: string + student_id?: string | null + mentor_id?: string | null + seat_limit?: number | null + participants?: number + title?: string | null + tags?: string[] | null + } + Relationships: [] + } + users: { + Row: { + created_at: string | null + email: string | null + id: string + learning_goals: string | null + name: string | null + skills: string | null + } + Insert: { + created_at?: string | null + email?: string | null + id?: string + learning_goals?: string | null + name?: string | null + skills?: string | null + } + Update: { + created_at?: string | null + email?: string | null + id?: string + learning_goals?: string | null + name?: string | null + skills?: string | null + } + Relationships: [] + } + } + Views: { + skill_endorsement_counts: { + Row: { + endorsed_user_id: string + skill: string + endorsement_count: number + } + Relationships: [] + } + } + Functions: { + submit_peer_review: { + Args: { + p_submission_id: string + p_feedback: string + } + Returns: { + id: string + submission_id: string + reviewer_id: string + feedback: string + rating: number | null + created_at: string + } + } + award_activity_xp: { + Args: { _activity_type: string } + Returns: undefined + } + get_user_rank: { + Args: { + p_user_id: string + p_filter?: string + } + Returns: number + } + has_role: { + Args: { + _role: string + _user_id: string + } + Returns: boolean + } + invite_to_study_room: { + Args: { + p_room_id: string + p_user_email: string + } + Returns: undefined + } + join_leaderboard: { + Args: { + _username: string + _avatar_url: string | null + } + Returns: undefined + } + join_public_study_room: { + Args: { p_room_id: string } + Returns: undefined + } + join_session: { + Args: { p_session_id: string } + Returns: undefined + } + mark_messages_as_read: { + Args: { message_ids: string[] } + Returns: undefined + } + tick_session_statuses: { + Args: Record + Returns: undefined + } + } + Enums: { + [_ in never]: never + } + CompositeTypes: { + [_ in never]: never + } + } +} + +type DatabaseWithoutInternals = Omit + +type DefaultSchema = DatabaseWithoutInternals[Extract] + +export type Tables< + DefaultSchemaTableNameOrOptions extends + | keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"]) + : never = never, +> = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals +} + ? (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends { + Row: infer R + } + ? R + : never + : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] & + DefaultSchema["Views"]) + ? (DefaultSchema["Tables"] & + DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends { + Row: infer R + } + ? R + : never + : never + +export type TablesInsert< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, +> = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals +} + ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Insert: infer I + } + ? I + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Insert: infer I + } + ? I + : never + : never + +export type TablesUpdate< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, +> = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals +} + ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Update: infer U + } + ? U + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Update: infer U + } + ? U + : never + : never + +export type Enums< + DefaultSchemaEnumNameOrOptions extends + | keyof DefaultSchema["Enums"] + | { schema: keyof DatabaseWithoutInternals }, + EnumName extends DefaultSchemaEnumNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"] + : never = never, +> = DefaultSchemaEnumNameOrOptions extends { + schema: keyof DatabaseWithoutInternals +} + ? DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName] + : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"] + ? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions] + : never + +export type CompositeTypes< + PublicCompositeTypeNameOrOptions extends + | keyof DefaultSchema["CompositeTypes"] + | { schema: keyof DatabaseWithoutInternals }, + CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"] + : never = never, +> = PublicCompositeTypeNameOrOptions extends { + schema: keyof DatabaseWithoutInternals +} + ? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName] + : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"] + ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions] + : never + +export const Constants = { + public: { + Enums: {}, + }, +} as const diff --git a/src/lib/http.ts b/src/lib/http.ts index 9723da71..e48bd920 100644 --- a/src/lib/http.ts +++ b/src/lib/http.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ + import { toast } from "sonner"; type UnknownRecord = Record; diff --git a/src/lib/rewardXP.ts b/src/lib/rewardXP.ts index d091fcec..33bb9ae9 100644 --- a/src/lib/rewardXP.ts +++ b/src/lib/rewardXP.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ + import { supabase } from "@/integrations/supabase/client"; export const rewardXP = async ( diff --git a/src/lib/streakSystem.ts b/src/lib/streakSystem.ts index df4b4e52..c2fcc02f 100644 --- a/src/lib/streakSystem.ts +++ b/src/lib/streakSystem.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ + /** * streakSystem.ts * diff --git a/src/main.tsx b/src/main.tsx index a4b0f28d..8cacb856 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,4 +1,18 @@ import { createRoot } from "react-dom/client"; +import * as Sentry from "@sentry/react"; + +Sentry.init({ + dsn: import.meta.env.VITE_SENTRY_DSN || "", + integrations: [ + Sentry.browserTracingIntegration(), + Sentry.replayIntegration(), + ], + // Tracing + tracesSampleRate: import.meta.env.PROD ? 0.1 : 1.0, + // Session Replay + replaysSessionSampleRate: import.meta.env.PROD ? 0.01 : 0.1, + replaysOnErrorSampleRate: import.meta.env.PROD ? 0.5 : 1.0, +}); import App from "./App.tsx"; import ErrorBoundary from "./components/ErrorBoundary.tsx"; import "./index.css"; diff --git a/src/pages/Chat.tsx b/src/pages/Chat.tsx index 7a061d44..a33b748b 100644 --- a/src/pages/Chat.tsx +++ b/src/pages/Chat.tsx @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ + import React, { memo, Suspense, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; import { ArrowLeft, MessageCircle, Search, Send } from "lucide-react"; import { useChatShortcuts } from "@/hooks/useChatShortcuts"; diff --git a/src/pages/Contact.test.tsx b/src/pages/Contact.test.tsx index ca4459e0..3ffa3345 100644 --- a/src/pages/Contact.test.tsx +++ b/src/pages/Contact.test.tsx @@ -29,7 +29,7 @@ describe("Contact", () => { beforeEach(() => { vi.clearAllMocks(); - localStorage?.clear(); + localStorage?.clear?.(); (useToast as any).mockReturnValue({ toast }); (supabase.from as any).mockReturnValue({ insert }); diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 9a49c9bd..3887c533 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ + import { Suspense, lazy, useCallback, useEffect, useState } from "react"; import { motion } from "framer-motion"; import { useNavigate } from "react-router-dom"; diff --git a/src/pages/Discover.tsx b/src/pages/Discover.tsx index 3c351ac2..c2f32749 100644 --- a/src/pages/Discover.tsx +++ b/src/pages/Discover.tsx @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ + import { memo, useCallback, useEffect, useMemo, useState } from "react"; import { motion } from "framer-motion"; import { @@ -16,6 +16,9 @@ import { supabase } from "@/integrations/supabase/client"; import { NotificationsDropdown } from "@/components/NotificationsDropdown"; import { Check } from "lucide-react"; import { API_BASE_URL } from "@/config/api"; +import { Database } from "@/integrations/supabase/types"; + +type Profile = Database["public"]["Tables"]["profiles"]["Row"]; const filters = [ "All", @@ -48,7 +51,7 @@ const cardVariants = { }, }; -const DiscoverPeerCard = memo(({ user, isOnline, onConnect, isConnected }: any) => { +const DiscoverPeerCard = memo(({ user, isOnline, onConnect, isConnected }: { user: Profile; isOnline: boolean; onConnect: (id: string) => void; isConnected: boolean }) => { return (
- {(Array.isArray(user.skills) ? user.skills : (user.skills?.split(",") || [])).map((skill: string, index: number) => ( + {(user.skills || []).slice(0, 3).map((skill: string, index: number) => ( {typeof skill === 'string' ? skill.trim() : skill} @@ -106,11 +109,11 @@ const DiscoverPeerCard = memo(({ user, isOnline, onConnect, isConnected }: any) const Discover = () => { const [currentUser, setCurrentUser] = - useState(null); + useState(null); - const [users, setUsers] = useState([]); + const [users, setUsers] = useState([]); const [filteredUsers, setFilteredUsers] = - useState([]); + useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); diff --git a/src/pages/Leaderboard.tsx b/src/pages/Leaderboard.tsx index 43f56af3..39343767 100644 --- a/src/pages/Leaderboard.tsx +++ b/src/pages/Leaderboard.tsx @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ + import { memo, useEffect, useRef, useState, useCallback } from "react"; import { motion } from "framer-motion"; import { diff --git a/src/pages/MentorDashboard.tsx b/src/pages/MentorDashboard.tsx index 9bd9dc29..18190955 100644 --- a/src/pages/MentorDashboard.tsx +++ b/src/pages/MentorDashboard.tsx @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ + import { useEffect, useState } from "react"; import { useRole } from "@/contexts/RoleContext"; import { useAuth } from "@/contexts/useAuth"; diff --git a/src/pages/Notifications.tsx b/src/pages/Notifications.tsx index 3a7a1ad5..e99f76a1 100644 --- a/src/pages/Notifications.tsx +++ b/src/pages/Notifications.tsx @@ -1,7 +1,14 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { useEffect, useState } from "react"; import { supabase } from "@/integrations/supabase/client"; +interface NotificationRow { + id: string; + type: string; + content: string; + read: boolean; + created_at: string; +} + const Notifications = () => { const [alerts, setAlerts] = useState([]); @@ -24,9 +31,9 @@ const Notifications = () => {

Notifications

{alerts.length > 0 ? ( - alerts.map((a) => ( + alerts.map((a: NotificationRow) => (
- 📢 New Session: {a.title} + 📢 New Session: {a.content || "Untitled Session"}
)) ) : ( diff --git a/src/pages/aipage.tsx b/src/pages/aipage.tsx index c37336a5..5c41f969 100644 --- a/src/pages/aipage.tsx +++ b/src/pages/aipage.tsx @@ -1,3 +1,4 @@ + import { useState } from "react"; import { Bot, Send, User } from "lucide-react"; import { supabase } from "@/integrations/supabase/client"; diff --git a/tailwind.config.ts b/tailwind.config.ts index 8bc0aa2a..a1d86d3e 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-object-type, no-unsafe-finally, @typescript-eslint/no-unused-expressions, @typescript-eslint/ban-ts-comment, @typescript-eslint/no-require-imports */ + import type { Config } from "tailwindcss"; export default { diff --git a/uploads/profiles/profile-user-948-test-1784562399339-80652895.png b/uploads/profiles/profile-user-948-test-1784562399339-80652895.png new file mode 100644 index 00000000..8f0aa88d Binary files /dev/null and b/uploads/profiles/profile-user-948-test-1784562399339-80652895.png differ diff --git a/uploads/profiles/profile-user-948-test-1784562399345-949418920.png b/uploads/profiles/profile-user-948-test-1784562399345-949418920.png new file mode 100644 index 00000000..8f0aa88d Binary files /dev/null and b/uploads/profiles/profile-user-948-test-1784562399345-949418920.png differ diff --git a/uploads/profiles/profile-user-948-test-1784563190224-561008045.png b/uploads/profiles/profile-user-948-test-1784563190224-561008045.png new file mode 100644 index 00000000..8f0aa88d Binary files /dev/null and b/uploads/profiles/profile-user-948-test-1784563190224-561008045.png differ diff --git a/uploads/profiles/profile-user-948-test-1784563190229-851416550.png b/uploads/profiles/profile-user-948-test-1784563190229-851416550.png new file mode 100644 index 00000000..8f0aa88d Binary files /dev/null and b/uploads/profiles/profile-user-948-test-1784563190229-851416550.png differ