analytica-frontend-lib 1.1.6 → 1.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/Auth/AuthProvider/index.js +12 -0
  2. package/dist/Auth/AuthProvider/index.js.map +1 -1
  3. package/dist/Auth/AuthProvider/index.mjs +12 -0
  4. package/dist/Auth/AuthProvider/index.mjs.map +1 -1
  5. package/dist/Auth/ProtectedRoute/index.js +12 -0
  6. package/dist/Auth/ProtectedRoute/index.js.map +1 -1
  7. package/dist/Auth/ProtectedRoute/index.mjs +12 -0
  8. package/dist/Auth/ProtectedRoute/index.mjs.map +1 -1
  9. package/dist/Auth/PublicRoute/index.js +12 -0
  10. package/dist/Auth/PublicRoute/index.js.map +1 -1
  11. package/dist/Auth/PublicRoute/index.mjs +12 -0
  12. package/dist/Auth/PublicRoute/index.mjs.map +1 -1
  13. package/dist/Auth/getRootDomain/index.js +12 -0
  14. package/dist/Auth/getRootDomain/index.js.map +1 -1
  15. package/dist/Auth/getRootDomain/index.mjs +12 -0
  16. package/dist/Auth/getRootDomain/index.mjs.map +1 -1
  17. package/dist/Auth/index.d.mts +15 -1
  18. package/dist/Auth/index.d.ts +15 -1
  19. package/dist/Auth/index.js +12 -0
  20. package/dist/Auth/index.js.map +1 -1
  21. package/dist/Auth/index.mjs +12 -0
  22. package/dist/Auth/index.mjs.map +1 -1
  23. package/dist/Auth/useAuth/index.js +12 -0
  24. package/dist/Auth/useAuth/index.js.map +1 -1
  25. package/dist/Auth/useAuth/index.mjs +12 -0
  26. package/dist/Auth/useAuth/index.mjs.map +1 -1
  27. package/dist/Auth/useAuthGuard/index.js +12 -0
  28. package/dist/Auth/useAuthGuard/index.js.map +1 -1
  29. package/dist/Auth/useAuthGuard/index.mjs +12 -0
  30. package/dist/Auth/useAuthGuard/index.mjs.map +1 -1
  31. package/dist/Auth/useRouteAuth/index.js +12 -0
  32. package/dist/Auth/useRouteAuth/index.js.map +1 -1
  33. package/dist/Auth/useRouteAuth/index.mjs +12 -0
  34. package/dist/Auth/useRouteAuth/index.mjs.map +1 -1
  35. package/dist/Auth/withAuth/index.js +12 -0
  36. package/dist/Auth/withAuth/index.js.map +1 -1
  37. package/dist/Auth/withAuth/index.mjs +12 -0
  38. package/dist/Auth/withAuth/index.mjs.map +1 -1
  39. package/dist/Whiteboard/index.d.mts +34 -0
  40. package/dist/Whiteboard/index.d.ts +34 -0
  41. package/dist/Whiteboard/index.js +151 -0
  42. package/dist/Whiteboard/index.js.map +1 -0
  43. package/dist/Whiteboard/index.mjs +130 -0
  44. package/dist/Whiteboard/index.mjs.map +1 -0
  45. package/dist/index.css +47 -0
  46. package/dist/index.css.map +1 -1
  47. package/dist/index.d.mts +1 -0
  48. package/dist/index.d.ts +1 -0
  49. package/dist/index.js +354 -222
  50. package/dist/index.js.map +1 -1
  51. package/dist/index.mjs +324 -193
  52. package/dist/index.mjs.map +1 -1
  53. package/dist/styles.css +47 -0
  54. package/dist/styles.css.map +1 -1
  55. package/package.json +3 -2
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/Auth/Auth.tsx"],"sourcesContent":["import {\n createContext,\n useContext,\n useEffect,\n useState,\n ReactNode,\n ComponentType,\n useCallback,\n useMemo,\n} from 'react';\nimport { useLocation, Navigate } from 'react-router-dom';\n\n/**\n * Interface for basic authentication tokens\n *\n * @interface AuthTokens\n * @property {string} token - Main authentication token\n * @property {string} refreshToken - Token used to refresh the main token\n * @property {unknown} [key] - Additional properties that can be included\n */\nexport interface AuthTokens {\n token: string;\n refreshToken: string;\n [key: string]: unknown;\n}\n\n/**\n * Interface for basic user information\n *\n * @interface AuthUser\n * @property {string} id - Unique user identifier\n * @property {string} [name] - Optional user name\n * @property {string} [email] - Optional user email\n * @property {unknown} [key] - Additional user properties\n */\nexport interface AuthUser {\n id: string;\n name?: string;\n email?: string;\n [key: string]: unknown;\n}\n\n/**\n * Interface for basic session information\n *\n * @interface SessionInfo\n * @property {string} [institutionId] - Optional institution identifier\n * @property {string} [profileId] - Optional profile identifier\n * @property {string} [schoolId] - Optional school identifier\n * @property {string} [schoolYearId] - Optional school year identifier\n * @property {string} [classId] - Optional class identifier\n * @property {unknown} [key] - Additional session properties\n */\nexport interface SessionInfo {\n institutionId?: string;\n profileId?: string;\n schoolId?: string;\n schoolYearId?: string;\n classId?: string;\n [key: string]: unknown;\n}\n\n/**\n * Interface for authentication state\n *\n * @interface AuthState\n * @property {boolean} isAuthenticated - Whether the user is authenticated\n * @property {boolean} isLoading - Whether authentication is being checked\n * @property {AuthUser | null} [user] - Current user information\n * @property {SessionInfo | null} [sessionInfo] - Current session information\n * @property {AuthTokens | null} [tokens] - Current authentication tokens\n */\nexport interface AuthState {\n isAuthenticated: boolean;\n isLoading: boolean;\n user?: AuthUser | null;\n sessionInfo?: SessionInfo | null;\n tokens?: AuthTokens | null;\n}\n\n/**\n * Interface for authentication context functions and state\n *\n * @interface AuthContextType\n * @extends {AuthState}\n * @property {() => Promise<boolean>} checkAuth - Function to check authentication status\n * @property {() => void} signOut - Function to sign out the user\n */\nexport interface AuthContextType extends AuthState {\n checkAuth: () => Promise<boolean>;\n signOut: () => void;\n}\n\n/**\n * Authentication context for React components\n *\n * @private\n */\nconst AuthContext = createContext<AuthContextType | undefined>(undefined);\n\n/**\n * Props for the AuthProvider component\n *\n * @interface AuthProviderProps\n * @property {ReactNode} children - Child components\n * @property {() => Promise<boolean> | boolean} [checkAuthFn] - Function to check if user is authenticated\n * @property {() => void} [signOutFn] - Function to handle logout\n * @property {Partial<AuthState>} [initialAuthState] - Initial authentication state\n * @property {() => AuthUser | null | undefined} [getUserFn] - Function to get user data\n * @property {() => SessionInfo | null | undefined} [getSessionFn] - Function to get session info\n * @property {() => AuthTokens | null | undefined} [getTokensFn] - Function to get tokens\n */\nexport interface AuthProviderProps {\n children: ReactNode;\n /**\n * Função para verificar se o usuário está autenticado\n * Deve retornar uma Promise<boolean>\n */\n checkAuthFn?: () => Promise<boolean> | boolean;\n /**\n * Função para fazer logout\n */\n signOutFn?: () => void;\n /**\n * Estado de autenticação inicial\n */\n initialAuthState?: Partial<AuthState>;\n /**\n * Função para obter dados do usuário (opcional)\n */\n getUserFn?: () => AuthUser | null | undefined;\n /**\n * Função para obter informações da sessão (opcional)\n */\n getSessionFn?: () => SessionInfo | null | undefined;\n /**\n * Função para obter tokens (opcional)\n */\n getTokensFn?: () => AuthTokens | null | undefined;\n}\n\n/**\n * Authentication provider that manages global auth state\n * Compatible with any store (Zustand, Redux, Context, etc.)\n *\n * @param {AuthProviderProps} props - The provider props\n * @returns {JSX.Element} The provider component\n *\n * @example\n * ```tsx\n * <AuthProvider\n * checkAuthFn={checkAuthFunction}\n * signOutFn={signOutFunction}\n * getUserFn={getUserFunction}\n * >\n * <App />\n * </AuthProvider>\n * ```\n */\nexport const AuthProvider = ({\n children,\n checkAuthFn,\n signOutFn,\n initialAuthState = {},\n getUserFn,\n getSessionFn,\n getTokensFn,\n}: AuthProviderProps) => {\n const [authState, setAuthState] = useState<AuthState>({\n isAuthenticated: false,\n isLoading: true,\n ...initialAuthState,\n });\n\n /**\n * Check authentication status and update state accordingly\n *\n * @returns {Promise<boolean>} Promise that resolves to authentication status\n */\n const checkAuth = useCallback(async (): Promise<boolean> => {\n try {\n setAuthState((prev) => ({ ...prev, isLoading: true }));\n\n // Se não há função de verificação, assume como não autenticado\n if (!checkAuthFn) {\n setAuthState((prev) => ({\n ...prev,\n isAuthenticated: false,\n isLoading: false,\n }));\n return false;\n }\n\n const isAuth = await checkAuthFn();\n\n setAuthState((prev) => ({\n ...prev,\n isAuthenticated: isAuth,\n isLoading: false,\n user: getUserFn ? getUserFn() : prev.user,\n sessionInfo: getSessionFn ? getSessionFn() : prev.sessionInfo,\n tokens: getTokensFn ? getTokensFn() : prev.tokens,\n }));\n\n return isAuth;\n } catch (error) {\n console.error('Erro ao verificar autenticação:', error);\n setAuthState((prev) => ({\n ...prev,\n isAuthenticated: false,\n isLoading: false,\n }));\n return false;\n }\n }, [checkAuthFn, getUserFn, getSessionFn, getTokensFn]);\n\n /**\n * Sign out the current user and clear auth state\n *\n * @returns {void}\n */\n const signOut = useCallback(() => {\n if (signOutFn) {\n signOutFn();\n }\n setAuthState((prev) => ({\n ...prev,\n isAuthenticated: false,\n user: undefined,\n sessionInfo: undefined,\n tokens: undefined,\n }));\n }, [signOutFn]);\n\n useEffect(() => {\n checkAuth();\n }, [checkAuth]);\n\n const contextValue = useMemo(\n (): AuthContextType => ({\n ...authState,\n checkAuth,\n signOut,\n }),\n [authState, checkAuth, signOut]\n );\n\n return (\n <AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>\n );\n};\n\n/**\n * Hook to use the authentication context\n *\n * @throws {Error} When used outside of AuthProvider\n * @returns {AuthContextType} The authentication context\n *\n * @example\n * ```tsx\n * const { isAuthenticated, user, signOut } = useAuth();\n * ```\n */\nexport const useAuth = (): AuthContextType => {\n const context = useContext(AuthContext);\n if (context === undefined) {\n throw new Error('useAuth deve ser usado dentro de um AuthProvider');\n }\n return context;\n};\n\n/**\n * Props for the ProtectedRoute component\n *\n * @interface ProtectedRouteProps\n * @property {ReactNode} children - Components to render when authenticated\n * @property {string} [redirectTo] - Path to redirect when not authenticated (default: '/')\n * @property {ReactNode} [loadingComponent] - Custom loading component\n * @property {(authState: AuthState) => boolean} [additionalCheck] - Additional authentication check\n */\nexport interface ProtectedRouteProps {\n children: ReactNode;\n /**\n * Path para redirecionamento quando não autenticado\n */\n redirectTo?: string;\n /**\n * Componente de loading personalizado\n */\n loadingComponent?: ReactNode;\n /**\n * Função adicional de verificação (ex: verificar permissões específicas)\n */\n additionalCheck?: (authState: AuthState) => boolean;\n}\n\n/**\n * Componente para proteger rotas que requerem autenticação\n *\n * @example\n * ```tsx\n * <ProtectedRoute redirectTo=\"/login\">\n * <PainelPage />\n * </ProtectedRoute>\n * ```\n */\nexport const ProtectedRoute = ({\n children,\n redirectTo = '/',\n loadingComponent,\n additionalCheck,\n}: ProtectedRouteProps) => {\n const { isAuthenticated, isLoading, ...authState } = useAuth();\n\n // Componente de loading padrão\n const defaultLoadingComponent = (\n <div className=\"flex items-center justify-center min-h-screen\">\n <div className=\"text-text-950 text-lg\">Carregando...</div>\n </div>\n );\n\n // Mostrar loading enquanto verifica autenticação\n if (isLoading) {\n return <>{loadingComponent || defaultLoadingComponent}</>;\n }\n\n // Verificar autenticação básica\n if (!isAuthenticated) {\n if (typeof window !== 'undefined') {\n const rootDomain = getRootDomain();\n // Only redirect if the root domain is different from current location\n const currentLocation = `${window.location.protocol}//${window.location.hostname}${window.location.port ? ':' + window.location.port : ''}`;\n if (rootDomain !== currentLocation) {\n window.location.href = rootDomain;\n return null;\n }\n }\n return <Navigate to={redirectTo} replace />;\n }\n\n // Verificação adicional (ex: permissões)\n if (\n additionalCheck &&\n !additionalCheck({ isAuthenticated, isLoading, ...authState })\n ) {\n return <Navigate to={redirectTo} replace />;\n }\n\n return <>{children}</>;\n};\n\n/**\n * Props for the PublicRoute component\n *\n * @interface PublicRouteProps\n * @property {ReactNode} children - Components to render\n * @property {string} [redirectTo] - Path to redirect to (default: '/painel')\n * @property {boolean} [redirectIfAuthenticated] - Whether to redirect if authenticated\n * @property {boolean} [checkAuthBeforeRender] - Whether to check auth before rendering\n */\nexport interface PublicRouteProps {\n children: ReactNode;\n /**\n * Path para redirecionamento\n */\n redirectTo?: string;\n /**\n * Se deve redirecionar quando usuário estiver autenticado\n */\n redirectIfAuthenticated?: boolean;\n /**\n * Se deve verificar autenticação antes de renderizar\n */\n checkAuthBeforeRender?: boolean;\n}\n\n/**\n * Componente para rotas públicas (login, recuperação de senha, etc.)\n * Opcionalmente redireciona se o usuário já estiver autenticado\n *\n * @example\n * ```tsx\n * <PublicRoute redirectTo=\"/painel\" redirectIfAuthenticated={true}>\n * <LoginPage />\n * </PublicRoute>\n * ```\n */\nexport const PublicRoute = ({\n children,\n redirectTo = '/painel',\n redirectIfAuthenticated = false,\n checkAuthBeforeRender = false,\n}: PublicRouteProps) => {\n const { isAuthenticated, isLoading } = useAuth();\n\n // Se deve aguardar verificação de auth antes de renderizar\n if (checkAuthBeforeRender && isLoading) {\n return (\n <div className=\"flex items-center justify-center min-h-screen\">\n <div className=\"text-text-950 text-lg\">Carregando...</div>\n </div>\n );\n }\n\n // Redirecionar se já autenticado e configurado para isso\n if (isAuthenticated && redirectIfAuthenticated) {\n return <Navigate to={redirectTo} replace />;\n }\n\n return <>{children}</>;\n};\n\n/**\n * Higher-Order Component to protect components with authentication\n *\n * @template P - Component props type\n * @param {ComponentType<P>} Component - Component to wrap with authentication\n * @param {Omit<ProtectedRouteProps, 'children'>} [options] - Protection options\n * @returns {(props: P) => JSX.Element} Wrapped component\n *\n * @example\n * ```tsx\n * const ProtectedComponent = withAuth(MyComponent, {\n * redirectTo: \"/login\",\n * loadingComponent: <CustomSpinner />\n * });\n * ```\n */\nexport const withAuth = <P extends object>(\n Component: ComponentType<P>,\n options: Omit<ProtectedRouteProps, 'children'> = {}\n) => {\n return (props: P) => (\n <ProtectedRoute {...options}>\n <Component {...props} />\n </ProtectedRoute>\n );\n};\n\n/**\n * Hook for authentication guard with custom checks\n *\n * @param {object} [options] - Guard options\n * @param {boolean} [options.requireAuth=true] - Whether authentication is required\n * @param {(authState: AuthState) => boolean} [options.customCheck] - Custom check function\n * @returns {object} Guard result with canAccess, isLoading, and authState\n *\n * @example\n * ```tsx\n * const { canAccess, isLoading } = useAuthGuard({\n * requireAuth: true,\n * customCheck: (authState) => authState.user?.role === 'admin'\n * });\n * ```\n */\nexport const useAuthGuard = (\n options: {\n requireAuth?: boolean;\n customCheck?: (authState: AuthState) => boolean;\n } = {}\n) => {\n const authState = useAuth();\n const { requireAuth = true, customCheck } = options;\n\n const canAccess =\n !authState.isLoading &&\n (requireAuth\n ? authState.isAuthenticated && (!customCheck || customCheck(authState))\n : !authState.isAuthenticated || !customCheck || customCheck(authState));\n\n return {\n canAccess,\n isLoading: authState.isLoading,\n authState,\n };\n};\n\n/**\n * Hook to check authentication on specific routes\n * Useful for conditional checks within components\n *\n * @param {string} [fallbackPath='/'] - Path to redirect when not authenticated\n * @returns {object} Object with isAuthenticated, isLoading, and redirectToLogin function\n *\n * @example\n * ```tsx\n * const { isAuthenticated, redirectToLogin } = useRouteAuth();\n *\n * if (!isAuthenticated) {\n * return redirectToLogin();\n * }\n * ```\n */\nexport const useRouteAuth = (fallbackPath = '/') => {\n const { isAuthenticated, isLoading } = useAuth();\n const location = useLocation();\n\n const redirectToLogin = () => (\n <Navigate to={fallbackPath} state={{ from: location }} replace />\n );\n\n return {\n isAuthenticated,\n isLoading,\n redirectToLogin,\n };\n};\n\n/**\n * Get the root domain from the current window location\n * Handles localhost and subdomain cases\n *\n * @returns {string} The root domain URL\n */\nexport const getRootDomain = () => {\n const { hostname, protocol, port } = window.location;\n const portStr = port ? ':' + port : '';\n if (hostname === 'localhost') {\n return `${protocol}//${hostname}${portStr}`;\n }\n const parts = hostname.split('.');\n // Only treat as subdomain if there are 3+ parts (e.g., subdomain.example.com)\n if (parts.length > 2) {\n // Return the last 2 parts as the root domain (example.com)\n const base = parts.slice(-2).join('.');\n return `${protocol}//${base}${portStr}`;\n }\n // For 2-part domains (example.com) or single domains, return as-is\n return `${protocol}//${hostname}${portStr}`;\n};\n\nexport default {\n AuthProvider,\n ProtectedRoute,\n PublicRoute,\n withAuth,\n useAuth,\n useAuthGuard,\n useRouteAuth,\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/components/Auth/Auth.tsx"],"sourcesContent":["import {\n createContext,\n useContext,\n useEffect,\n useState,\n ReactNode,\n ComponentType,\n useCallback,\n useMemo,\n} from 'react';\nimport { useLocation, Navigate } from 'react-router-dom';\n\n/**\n * Interface for basic authentication tokens\n *\n * @interface AuthTokens\n * @property {string} token - Main authentication token\n * @property {string} refreshToken - Token used to refresh the main token\n * @property {unknown} [key] - Additional properties that can be included\n */\nexport interface AuthTokens {\n token: string;\n refreshToken: string;\n [key: string]: unknown;\n}\n\n/**\n * Interface for basic user information\n *\n * @interface AuthUser\n * @property {string} id - Unique user identifier\n * @property {string} [name] - Optional user name\n * @property {string} [email] - Optional user email\n * @property {unknown} [key] - Additional user properties\n */\nexport interface AuthUser {\n id: string;\n name?: string;\n email?: string;\n [key: string]: unknown;\n}\n\n/**\n * Interface for basic session information\n *\n * @interface SessionInfo\n * @property {string} [institutionId] - Optional institution identifier\n * @property {string} [profileId] - Optional profile identifier\n * @property {string} [schoolId] - Optional school identifier\n * @property {string} [schoolYearId] - Optional school year identifier\n * @property {string} [classId] - Optional class identifier\n * @property {unknown} [key] - Additional session properties\n */\nexport interface SessionInfo {\n institutionId?: string;\n profileId?: string;\n schoolId?: string;\n schoolYearId?: string;\n classId?: string;\n [key: string]: unknown;\n}\n\n/**\n * Interface for authentication state\n *\n * @interface AuthState\n * @property {boolean} isAuthenticated - Whether the user is authenticated\n * @property {boolean} isLoading - Whether authentication is being checked\n * @property {AuthUser | null} [user] - Current user information\n * @property {SessionInfo | null} [sessionInfo] - Current session information\n * @property {AuthTokens | null} [tokens] - Current authentication tokens\n */\nexport interface AuthState {\n isAuthenticated: boolean;\n isLoading: boolean;\n user?: AuthUser | null;\n sessionInfo?: SessionInfo | null;\n tokens?: AuthTokens | null;\n}\n\n/**\n * Interface for authentication context functions and state\n *\n * @interface AuthContextType\n * @extends {AuthState}\n * @property {() => Promise<boolean>} checkAuth - Function to check authentication status\n * @property {() => void} signOut - Function to sign out the user\n */\nexport interface AuthContextType extends AuthState {\n checkAuth: () => Promise<boolean>;\n signOut: () => void;\n}\n\n/**\n * Authentication context for React components\n *\n * @private\n */\nconst AuthContext = createContext<AuthContextType | undefined>(undefined);\n\n/**\n * Props for the AuthProvider component\n *\n * @interface AuthProviderProps\n * @property {ReactNode} children - Child components\n * @property {() => Promise<boolean> | boolean} [checkAuthFn] - Function to check if user is authenticated\n * @property {() => void} [signOutFn] - Function to handle logout\n * @property {Partial<AuthState>} [initialAuthState] - Initial authentication state\n * @property {() => AuthUser | null | undefined} [getUserFn] - Function to get user data\n * @property {() => SessionInfo | null | undefined} [getSessionFn] - Function to get session info\n * @property {() => AuthTokens | null | undefined} [getTokensFn] - Function to get tokens\n */\nexport interface AuthProviderProps {\n children: ReactNode;\n /**\n * Função para verificar se o usuário está autenticado\n * Deve retornar uma Promise<boolean>\n */\n checkAuthFn?: () => Promise<boolean> | boolean;\n /**\n * Função para fazer logout\n */\n signOutFn?: () => void;\n /**\n * Estado de autenticação inicial\n */\n initialAuthState?: Partial<AuthState>;\n /**\n * Função para obter dados do usuário (opcional)\n */\n getUserFn?: () => AuthUser | null | undefined;\n /**\n * Função para obter informações da sessão (opcional)\n */\n getSessionFn?: () => SessionInfo | null | undefined;\n /**\n * Função para obter tokens (opcional)\n */\n getTokensFn?: () => AuthTokens | null | undefined;\n}\n\n/**\n * Authentication provider that manages global auth state\n * Compatible with any store (Zustand, Redux, Context, etc.)\n *\n * @param {AuthProviderProps} props - The provider props\n * @returns {JSX.Element} The provider component\n *\n * @example\n * ```tsx\n * <AuthProvider\n * checkAuthFn={checkAuthFunction}\n * signOutFn={signOutFunction}\n * getUserFn={getUserFunction}\n * >\n * <App />\n * </AuthProvider>\n * ```\n */\nexport const AuthProvider = ({\n children,\n checkAuthFn,\n signOutFn,\n initialAuthState = {},\n getUserFn,\n getSessionFn,\n getTokensFn,\n}: AuthProviderProps) => {\n const [authState, setAuthState] = useState<AuthState>({\n isAuthenticated: false,\n isLoading: true,\n ...initialAuthState,\n });\n\n /**\n * Check authentication status and update state accordingly\n *\n * @returns {Promise<boolean>} Promise that resolves to authentication status\n */\n const checkAuth = useCallback(async (): Promise<boolean> => {\n try {\n setAuthState((prev) => ({ ...prev, isLoading: true }));\n\n // Se não há função de verificação, assume como não autenticado\n if (!checkAuthFn) {\n setAuthState((prev) => ({\n ...prev,\n isAuthenticated: false,\n isLoading: false,\n }));\n return false;\n }\n\n const isAuth = await checkAuthFn();\n\n setAuthState((prev) => ({\n ...prev,\n isAuthenticated: isAuth,\n isLoading: false,\n user: getUserFn ? getUserFn() : prev.user,\n sessionInfo: getSessionFn ? getSessionFn() : prev.sessionInfo,\n tokens: getTokensFn ? getTokensFn() : prev.tokens,\n }));\n\n return isAuth;\n } catch (error) {\n console.error('Erro ao verificar autenticação:', error);\n setAuthState((prev) => ({\n ...prev,\n isAuthenticated: false,\n isLoading: false,\n }));\n return false;\n }\n }, [checkAuthFn, getUserFn, getSessionFn, getTokensFn]);\n\n /**\n * Sign out the current user and clear auth state\n *\n * @returns {void}\n */\n const signOut = useCallback(() => {\n if (signOutFn) {\n signOutFn();\n }\n setAuthState((prev) => ({\n ...prev,\n isAuthenticated: false,\n user: undefined,\n sessionInfo: undefined,\n tokens: undefined,\n }));\n }, [signOutFn]);\n\n useEffect(() => {\n checkAuth();\n }, [checkAuth]);\n\n const contextValue = useMemo(\n (): AuthContextType => ({\n ...authState,\n checkAuth,\n signOut,\n }),\n [authState, checkAuth, signOut]\n );\n\n return (\n <AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>\n );\n};\n\n/**\n * Hook to use the authentication context\n *\n * @throws {Error} When used outside of AuthProvider\n * @returns {AuthContextType} The authentication context\n *\n * @example\n * ```tsx\n * const { isAuthenticated, user, signOut } = useAuth();\n * ```\n */\nexport const useAuth = (): AuthContextType => {\n const context = useContext(AuthContext);\n if (context === undefined) {\n throw new Error('useAuth deve ser usado dentro de um AuthProvider');\n }\n return context;\n};\n\n/**\n * Props for the ProtectedRoute component\n *\n * @interface ProtectedRouteProps\n * @property {ReactNode} children - Components to render when authenticated\n * @property {string} [redirectTo] - Path to redirect when not authenticated (default: '/')\n * @property {ReactNode} [loadingComponent] - Custom loading component\n * @property {(authState: AuthState) => boolean} [additionalCheck] - Additional authentication check\n */\nexport interface ProtectedRouteProps {\n children: ReactNode;\n /**\n * Path para redirecionamento quando não autenticado\n */\n redirectTo?: string;\n /**\n * Componente de loading personalizado\n */\n loadingComponent?: ReactNode;\n /**\n * Função adicional de verificação (ex: verificar permissões específicas)\n */\n additionalCheck?: (authState: AuthState) => boolean;\n}\n\n/**\n * Componente para proteger rotas que requerem autenticação\n *\n * @example\n * ```tsx\n * <ProtectedRoute redirectTo=\"/login\">\n * <PainelPage />\n * </ProtectedRoute>\n * ```\n */\nexport const ProtectedRoute = ({\n children,\n redirectTo = '/',\n loadingComponent,\n additionalCheck,\n}: ProtectedRouteProps) => {\n const { isAuthenticated, isLoading, ...authState } = useAuth();\n\n // Componente de loading padrão\n const defaultLoadingComponent = (\n <div className=\"flex items-center justify-center min-h-screen\">\n <div className=\"text-text-950 text-lg\">Carregando...</div>\n </div>\n );\n\n // Mostrar loading enquanto verifica autenticação\n if (isLoading) {\n return <>{loadingComponent || defaultLoadingComponent}</>;\n }\n\n // Verificar autenticação básica\n if (!isAuthenticated) {\n if (typeof window !== 'undefined') {\n const rootDomain = getRootDomain();\n // Only redirect if the root domain is different from current location\n const currentLocation = `${window.location.protocol}//${window.location.hostname}${window.location.port ? ':' + window.location.port : ''}`;\n if (rootDomain !== currentLocation) {\n window.location.href = rootDomain;\n return null;\n }\n }\n return <Navigate to={redirectTo} replace />;\n }\n\n // Verificação adicional (ex: permissões)\n if (\n additionalCheck &&\n !additionalCheck({ isAuthenticated, isLoading, ...authState })\n ) {\n return <Navigate to={redirectTo} replace />;\n }\n\n return <>{children}</>;\n};\n\n/**\n * Props for the PublicRoute component\n *\n * @interface PublicRouteProps\n * @property {ReactNode} children - Components to render\n * @property {string} [redirectTo] - Path to redirect to (default: '/painel')\n * @property {boolean} [redirectIfAuthenticated] - Whether to redirect if authenticated\n * @property {boolean} [checkAuthBeforeRender] - Whether to check auth before rendering\n */\nexport interface PublicRouteProps {\n children: ReactNode;\n /**\n * Path para redirecionamento\n */\n redirectTo?: string;\n /**\n * Se deve redirecionar quando usuário estiver autenticado\n */\n redirectIfAuthenticated?: boolean;\n /**\n * Se deve verificar autenticação antes de renderizar\n */\n checkAuthBeforeRender?: boolean;\n}\n\n/**\n * Componente para rotas públicas (login, recuperação de senha, etc.)\n * Opcionalmente redireciona se o usuário já estiver autenticado\n *\n * @example\n * ```tsx\n * <PublicRoute redirectTo=\"/painel\" redirectIfAuthenticated={true}>\n * <LoginPage />\n * </PublicRoute>\n * ```\n */\nexport const PublicRoute = ({\n children,\n redirectTo = '/painel',\n redirectIfAuthenticated = false,\n checkAuthBeforeRender = false,\n}: PublicRouteProps) => {\n const { isAuthenticated, isLoading } = useAuth();\n\n // Se deve aguardar verificação de auth antes de renderizar\n if (checkAuthBeforeRender && isLoading) {\n return (\n <div className=\"flex items-center justify-center min-h-screen\">\n <div className=\"text-text-950 text-lg\">Carregando...</div>\n </div>\n );\n }\n\n // Redirecionar se já autenticado e configurado para isso\n if (isAuthenticated && redirectIfAuthenticated) {\n return <Navigate to={redirectTo} replace />;\n }\n\n return <>{children}</>;\n};\n\n/**\n * Higher-Order Component to protect components with authentication\n *\n * @template P - Component props type\n * @param {ComponentType<P>} Component - Component to wrap with authentication\n * @param {Omit<ProtectedRouteProps, 'children'>} [options] - Protection options\n * @returns {(props: P) => JSX.Element} Wrapped component\n *\n * @example\n * ```tsx\n * const ProtectedComponent = withAuth(MyComponent, {\n * redirectTo: \"/login\",\n * loadingComponent: <CustomSpinner />\n * });\n * ```\n */\nexport const withAuth = <P extends object>(\n Component: ComponentType<P>,\n options: Omit<ProtectedRouteProps, 'children'> = {}\n) => {\n return (props: P) => (\n <ProtectedRoute {...options}>\n <Component {...props} />\n </ProtectedRoute>\n );\n};\n\n/**\n * Hook for authentication guard with custom checks\n *\n * @param {object} [options] - Guard options\n * @param {boolean} [options.requireAuth=true] - Whether authentication is required\n * @param {(authState: AuthState) => boolean} [options.customCheck] - Custom check function\n * @returns {object} Guard result with canAccess, isLoading, and authState\n *\n * @example\n * ```tsx\n * const { canAccess, isLoading } = useAuthGuard({\n * requireAuth: true,\n * customCheck: (authState) => authState.user?.role === 'admin'\n * });\n * ```\n */\nexport const useAuthGuard = (\n options: {\n requireAuth?: boolean;\n customCheck?: (authState: AuthState) => boolean;\n } = {}\n) => {\n const authState = useAuth();\n const { requireAuth = true, customCheck } = options;\n\n const canAccess =\n !authState.isLoading &&\n (requireAuth\n ? authState.isAuthenticated && (!customCheck || customCheck(authState))\n : !authState.isAuthenticated || !customCheck || customCheck(authState));\n\n return {\n canAccess,\n isLoading: authState.isLoading,\n authState,\n };\n};\n\n/**\n * Hook to check authentication on specific routes\n * Useful for conditional checks within components\n *\n * @param {string} [fallbackPath='/'] - Path to redirect when not authenticated\n * @returns {object} Object with isAuthenticated, isLoading, and redirectToLogin function\n *\n * @example\n * ```tsx\n * const { isAuthenticated, redirectToLogin } = useRouteAuth();\n *\n * if (!isAuthenticated) {\n * return redirectToLogin();\n * }\n * ```\n */\nexport const useRouteAuth = (fallbackPath = '/') => {\n const { isAuthenticated, isLoading } = useAuth();\n const location = useLocation();\n\n const redirectToLogin = () => (\n <Navigate to={fallbackPath} state={{ from: location }} replace />\n );\n\n return {\n isAuthenticated,\n isLoading,\n redirectToLogin,\n };\n};\n\n/**\n * Get the root domain from the current window location\n * Handles localhost, IP addresses, and subdomain cases, including Brazilian .com.br domains\n *\n * @returns {string} The root domain URL\n *\n * @example\n * ```typescript\n * // Domain examples\n * aluno.analiticaensino.com.br -> analiticaensino.com.br\n * subdomain.example.com -> example.com\n *\n * // IP address examples\n * 127.0.0.1:3000 -> 127.0.0.1:3000\n * [::1]:8080 -> [::1]:8080\n *\n * // Localhost examples\n * localhost:3000 -> localhost:3000\n * ```\n */\nexport const getRootDomain = () => {\n const { hostname, protocol, port } = window.location;\n const portStr = port ? ':' + port : '';\n\n if (hostname === 'localhost') {\n return `${protocol}//${hostname}${portStr}`;\n }\n\n // IP literals: return as-is (no subdomain logic)\n const isIPv4 = /^\\d{1,3}(?:\\.\\d{1,3}){3}$/.test(hostname);\n const isIPv6 = hostname.includes(':'); // simple check is sufficient here\n if (isIPv4 || isIPv6) {\n return `${protocol}//${hostname}${portStr}`;\n }\n\n const parts = hostname.split('.');\n\n // Handle Brazilian .com.br domains and similar patterns\n if (\n parts.length >= 3 &&\n parts[parts.length - 2] === 'com' &&\n parts[parts.length - 1] === 'br'\n ) {\n if (parts.length === 3) {\n // Already at root level for .com.br (e.g., analiticaensino.com.br)\n return `${protocol}//${hostname}${portStr}`;\n }\n // For domains like aluno.analiticaensino.com.br, return analiticaensino.com.br\n const base = parts.slice(-3).join('.');\n return `${protocol}//${base}${portStr}`;\n }\n\n // Only treat as subdomain if there are 3+ parts (e.g., subdomain.example.com)\n if (parts.length > 2) {\n // Return the last 2 parts as the root domain (example.com)\n const base = parts.slice(-2).join('.');\n return `${protocol}//${base}${portStr}`;\n }\n\n // For 2-part domains (example.com) or single domains, return as-is\n return `${protocol}//${hostname}${portStr}`;\n};\n\nexport default {\n AuthProvider,\n ProtectedRoute,\n PublicRoute,\n withAuth,\n useAuth,\n useAuthGuard,\n useRouteAuth,\n};\n"],"mappings":"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","names":[]}
@@ -0,0 +1,34 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { HTMLAttributes } from 'react';
3
+
4
+ /**
5
+ * Whiteboard image item interface
6
+ */
7
+ interface WhiteboardImage {
8
+ id: string;
9
+ imageUrl: string;
10
+ title?: string;
11
+ }
12
+ /**
13
+ * Whiteboard component props interface
14
+ */
15
+ interface WhiteboardProps extends HTMLAttributes<HTMLDivElement> {
16
+ /** Array of images to display in the whiteboard */
17
+ images: WhiteboardImage[];
18
+ /** Whether to show download button on images */
19
+ showDownload?: boolean;
20
+ /** Custom className for the container */
21
+ className?: string;
22
+ /** Callback when download button is clicked */
23
+ onDownload?: (image: WhiteboardImage) => void;
24
+ /** Maximum number of images to display per row on desktop */
25
+ imagesPerRow?: 2 | 3 | 4;
26
+ }
27
+ /**
28
+ * Whiteboard component for displaying classroom board images
29
+ * @param props Component properties
30
+ * @returns Whiteboard component
31
+ */
32
+ declare const Whiteboard: ({ images, showDownload, className, onDownload, imagesPerRow, ...rest }: WhiteboardProps) => react_jsx_runtime.JSX.Element;
33
+
34
+ export { type WhiteboardImage, type WhiteboardProps, Whiteboard as default };
@@ -0,0 +1,34 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { HTMLAttributes } from 'react';
3
+
4
+ /**
5
+ * Whiteboard image item interface
6
+ */
7
+ interface WhiteboardImage {
8
+ id: string;
9
+ imageUrl: string;
10
+ title?: string;
11
+ }
12
+ /**
13
+ * Whiteboard component props interface
14
+ */
15
+ interface WhiteboardProps extends HTMLAttributes<HTMLDivElement> {
16
+ /** Array of images to display in the whiteboard */
17
+ images: WhiteboardImage[];
18
+ /** Whether to show download button on images */
19
+ showDownload?: boolean;
20
+ /** Custom className for the container */
21
+ className?: string;
22
+ /** Callback when download button is clicked */
23
+ onDownload?: (image: WhiteboardImage) => void;
24
+ /** Maximum number of images to display per row on desktop */
25
+ imagesPerRow?: 2 | 3 | 4;
26
+ }
27
+ /**
28
+ * Whiteboard component for displaying classroom board images
29
+ * @param props Component properties
30
+ * @returns Whiteboard component
31
+ */
32
+ declare const Whiteboard: ({ images, showDownload, className, onDownload, imagesPerRow, ...rest }: WhiteboardProps) => react_jsx_runtime.JSX.Element;
33
+
34
+ export { type WhiteboardImage, type WhiteboardProps, Whiteboard as default };
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/components/Whiteboard/Whiteboard.tsx
21
+ var Whiteboard_exports = {};
22
+ __export(Whiteboard_exports, {
23
+ default: () => Whiteboard_default
24
+ });
25
+ module.exports = __toCommonJS(Whiteboard_exports);
26
+ var import_react = require("react");
27
+ var import_phosphor_react = require("phosphor-react");
28
+
29
+ // src/utils/utils.ts
30
+ var import_clsx = require("clsx");
31
+ var import_tailwind_merge = require("tailwind-merge");
32
+ function cn(...inputs) {
33
+ return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
34
+ }
35
+
36
+ // src/components/Whiteboard/Whiteboard.tsx
37
+ var import_jsx_runtime = require("react/jsx-runtime");
38
+ var IMAGE_WIDTH = 225;
39
+ var IMAGE_HEIGHT = 90;
40
+ var Whiteboard = ({
41
+ images,
42
+ showDownload = true,
43
+ className,
44
+ onDownload,
45
+ imagesPerRow = 2,
46
+ ...rest
47
+ }) => {
48
+ const [imageErrors, setImageErrors] = (0, import_react.useState)(/* @__PURE__ */ new Set());
49
+ const handleDownload = (0, import_react.useCallback)(
50
+ (image) => {
51
+ if (onDownload) {
52
+ onDownload(image);
53
+ } else {
54
+ const link = document.createElement("a");
55
+ link.href = image.imageUrl;
56
+ link.download = image.title || `whiteboard-${image.id}`;
57
+ link.target = "_blank";
58
+ link.rel = "noopener noreferrer";
59
+ document.body.appendChild(link);
60
+ link.click();
61
+ document.body.removeChild(link);
62
+ }
63
+ },
64
+ [onDownload]
65
+ );
66
+ const handleImageError = (0, import_react.useCallback)((imageId) => {
67
+ setImageErrors((prev) => new Set(prev).add(imageId));
68
+ }, []);
69
+ const gridColsClass = images?.length === 1 ? "grid-cols-1" : {
70
+ 2: "grid-cols-1 sm:grid-cols-2",
71
+ 3: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
72
+ 4: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4"
73
+ }[imagesPerRow];
74
+ if (!images || images.length === 0) {
75
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
76
+ "div",
77
+ {
78
+ className: cn(
79
+ "flex items-center justify-center p-8 bg-white border border-gray-100 rounded-xl",
80
+ className
81
+ ),
82
+ ...rest,
83
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-gray-400 text-sm", children: "Nenhuma imagem dispon\xEDvel" })
84
+ }
85
+ );
86
+ }
87
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
88
+ "div",
89
+ {
90
+ className: cn(
91
+ "flex flex-col bg-white border border-gray-100 p-4 gap-2 rounded-xl w-fit mx-auto",
92
+ className
93
+ ),
94
+ ...rest,
95
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("grid gap-4", gridColsClass), children: images.map((image) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
96
+ "div",
97
+ {
98
+ className: "relative group overflow-hidden bg-gray-100 rounded-lg",
99
+ style: {
100
+ width: `${IMAGE_WIDTH}px`
101
+ },
102
+ children: [
103
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
104
+ "div",
105
+ {
106
+ className: "relative",
107
+ style: {
108
+ width: `${IMAGE_WIDTH}px`,
109
+ height: `${IMAGE_HEIGHT}px`
110
+ },
111
+ children: imageErrors.has(image.id) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute inset-0 flex items-center justify-center bg-gray-200", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-gray-500 text-sm text-center px-2", children: "Imagem indispon\xEDvel" }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
112
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
113
+ "img",
114
+ {
115
+ src: image.imageUrl,
116
+ alt: image.title || `Whiteboard ${image.id}`,
117
+ className: "absolute inset-0 w-full h-full object-cover",
118
+ loading: "lazy",
119
+ onError: () => handleImageError(image.id)
120
+ }
121
+ ),
122
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute inset-0 bg-gradient-to-t from-black/20 to-transparent" })
123
+ ] })
124
+ }
125
+ ),
126
+ showDownload && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
127
+ "button",
128
+ {
129
+ type: "button",
130
+ onClick: () => handleDownload(image),
131
+ className: "absolute bottom-3 right-3 flex items-center justify-center bg-black/20 backdrop-blur-sm rounded hover:bg-black/30 transition-colors duration-200 group/button w-6 h-6",
132
+ "aria-label": `Download ${image.title || "imagem"}`,
133
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
134
+ import_phosphor_react.DownloadSimple,
135
+ {
136
+ size: 24,
137
+ weight: "regular",
138
+ className: "text-white group-hover/button:scale-110 transition-transform duration-200"
139
+ }
140
+ )
141
+ }
142
+ )
143
+ ]
144
+ },
145
+ image.id
146
+ )) })
147
+ }
148
+ );
149
+ };
150
+ var Whiteboard_default = Whiteboard;
151
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/Whiteboard/Whiteboard.tsx","../../src/utils/utils.ts"],"sourcesContent":["import { HTMLAttributes, useCallback, useState } from 'react';\nimport { DownloadSimple } from 'phosphor-react';\nimport { cn } from '../../utils/utils';\n\n// Design constants for critical layout dimensions\nconst IMAGE_WIDTH = 225;\nconst IMAGE_HEIGHT = 90;\n\n/**\n * Whiteboard image item interface\n */\nexport interface WhiteboardImage {\n id: string;\n imageUrl: string;\n title?: string;\n}\n\n/**\n * Whiteboard component props interface\n */\nexport interface WhiteboardProps extends HTMLAttributes<HTMLDivElement> {\n /** Array of images to display in the whiteboard */\n images: WhiteboardImage[];\n /** Whether to show download button on images */\n showDownload?: boolean;\n /** Custom className for the container */\n className?: string;\n /** Callback when download button is clicked */\n onDownload?: (image: WhiteboardImage) => void;\n /** Maximum number of images to display per row on desktop */\n imagesPerRow?: 2 | 3 | 4;\n}\n\n/**\n * Whiteboard component for displaying classroom board images\n * @param props Component properties\n * @returns Whiteboard component\n */\nconst Whiteboard = ({\n images,\n showDownload = true,\n className,\n onDownload,\n imagesPerRow = 2,\n ...rest\n}: WhiteboardProps) => {\n // State to track images that failed to load\n const [imageErrors, setImageErrors] = useState<Set<string>>(new Set());\n\n /**\n * Handle image download\n */\n const handleDownload = useCallback(\n (image: WhiteboardImage) => {\n if (onDownload) {\n onDownload(image);\n } else {\n const link = document.createElement('a');\n link.href = image.imageUrl;\n link.download = image.title || `whiteboard-${image.id}`;\n link.target = '_blank';\n link.rel = 'noopener noreferrer';\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n }\n },\n [onDownload]\n );\n\n /**\n * Handle image loading error\n */\n const handleImageError = useCallback((imageId: string) => {\n setImageErrors((prev) => new Set(prev).add(imageId));\n }, []);\n\n const gridColsClass =\n images?.length === 1\n ? 'grid-cols-1'\n : {\n 2: 'grid-cols-1 sm:grid-cols-2',\n 3: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3',\n 4: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-4',\n }[imagesPerRow];\n\n // Let CSS handle sizing responsively\n\n if (!images || images.length === 0) {\n return (\n <div\n className={cn(\n 'flex items-center justify-center p-8 bg-white border border-gray-100 rounded-xl',\n className\n )}\n {...rest}\n >\n <p className=\"text-gray-400 text-sm\">Nenhuma imagem disponível</p>\n </div>\n );\n }\n\n return (\n <div\n className={cn(\n 'flex flex-col bg-white border border-gray-100 p-4 gap-2 rounded-xl w-fit mx-auto',\n className\n )}\n {...rest}\n >\n <div className={cn('grid gap-4', gridColsClass)}>\n {images.map((image) => (\n <div\n key={image.id}\n className=\"relative group overflow-hidden bg-gray-100 rounded-lg\"\n style={{\n width: `${IMAGE_WIDTH}px`,\n }}\n >\n <div\n className=\"relative\"\n style={{\n width: `${IMAGE_WIDTH}px`,\n height: `${IMAGE_HEIGHT}px`,\n }}\n >\n {imageErrors.has(image.id) ? (\n <div className=\"absolute inset-0 flex items-center justify-center bg-gray-200\">\n <p className=\"text-gray-500 text-sm text-center px-2\">\n Imagem indisponível\n </p>\n </div>\n ) : (\n <>\n <img\n src={image.imageUrl}\n alt={image.title || `Whiteboard ${image.id}`}\n className=\"absolute inset-0 w-full h-full object-cover\"\n loading=\"lazy\"\n onError={() => handleImageError(image.id)}\n />\n <div className=\"absolute inset-0 bg-gradient-to-t from-black/20 to-transparent\" />\n </>\n )}\n </div>\n {showDownload && (\n <button\n type=\"button\"\n onClick={() => handleDownload(image)}\n className=\"absolute bottom-3 right-3 flex items-center justify-center bg-black/20 backdrop-blur-sm rounded hover:bg-black/30 transition-colors duration-200 group/button w-6 h-6\"\n aria-label={`Download ${image.title || 'imagem'}`}\n >\n <DownloadSimple\n size={24}\n weight=\"regular\"\n className=\"text-white group-hover/button:scale-110 transition-transform duration-200\"\n />\n </button>\n )}\n </div>\n ))}\n </div>\n </div>\n );\n};\n\nexport default Whiteboard;\n","import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,130 @@
1
+ // src/components/Whiteboard/Whiteboard.tsx
2
+ import { useCallback, useState } from "react";
3
+ import { DownloadSimple } from "phosphor-react";
4
+
5
+ // src/utils/utils.ts
6
+ import { clsx } from "clsx";
7
+ import { twMerge } from "tailwind-merge";
8
+ function cn(...inputs) {
9
+ return twMerge(clsx(inputs));
10
+ }
11
+
12
+ // src/components/Whiteboard/Whiteboard.tsx
13
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
14
+ var IMAGE_WIDTH = 225;
15
+ var IMAGE_HEIGHT = 90;
16
+ var Whiteboard = ({
17
+ images,
18
+ showDownload = true,
19
+ className,
20
+ onDownload,
21
+ imagesPerRow = 2,
22
+ ...rest
23
+ }) => {
24
+ const [imageErrors, setImageErrors] = useState(/* @__PURE__ */ new Set());
25
+ const handleDownload = useCallback(
26
+ (image) => {
27
+ if (onDownload) {
28
+ onDownload(image);
29
+ } else {
30
+ const link = document.createElement("a");
31
+ link.href = image.imageUrl;
32
+ link.download = image.title || `whiteboard-${image.id}`;
33
+ link.target = "_blank";
34
+ link.rel = "noopener noreferrer";
35
+ document.body.appendChild(link);
36
+ link.click();
37
+ document.body.removeChild(link);
38
+ }
39
+ },
40
+ [onDownload]
41
+ );
42
+ const handleImageError = useCallback((imageId) => {
43
+ setImageErrors((prev) => new Set(prev).add(imageId));
44
+ }, []);
45
+ const gridColsClass = images?.length === 1 ? "grid-cols-1" : {
46
+ 2: "grid-cols-1 sm:grid-cols-2",
47
+ 3: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
48
+ 4: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4"
49
+ }[imagesPerRow];
50
+ if (!images || images.length === 0) {
51
+ return /* @__PURE__ */ jsx(
52
+ "div",
53
+ {
54
+ className: cn(
55
+ "flex items-center justify-center p-8 bg-white border border-gray-100 rounded-xl",
56
+ className
57
+ ),
58
+ ...rest,
59
+ children: /* @__PURE__ */ jsx("p", { className: "text-gray-400 text-sm", children: "Nenhuma imagem dispon\xEDvel" })
60
+ }
61
+ );
62
+ }
63
+ return /* @__PURE__ */ jsx(
64
+ "div",
65
+ {
66
+ className: cn(
67
+ "flex flex-col bg-white border border-gray-100 p-4 gap-2 rounded-xl w-fit mx-auto",
68
+ className
69
+ ),
70
+ ...rest,
71
+ children: /* @__PURE__ */ jsx("div", { className: cn("grid gap-4", gridColsClass), children: images.map((image) => /* @__PURE__ */ jsxs(
72
+ "div",
73
+ {
74
+ className: "relative group overflow-hidden bg-gray-100 rounded-lg",
75
+ style: {
76
+ width: `${IMAGE_WIDTH}px`
77
+ },
78
+ children: [
79
+ /* @__PURE__ */ jsx(
80
+ "div",
81
+ {
82
+ className: "relative",
83
+ style: {
84
+ width: `${IMAGE_WIDTH}px`,
85
+ height: `${IMAGE_HEIGHT}px`
86
+ },
87
+ children: imageErrors.has(image.id) ? /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-gray-200", children: /* @__PURE__ */ jsx("p", { className: "text-gray-500 text-sm text-center px-2", children: "Imagem indispon\xEDvel" }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
88
+ /* @__PURE__ */ jsx(
89
+ "img",
90
+ {
91
+ src: image.imageUrl,
92
+ alt: image.title || `Whiteboard ${image.id}`,
93
+ className: "absolute inset-0 w-full h-full object-cover",
94
+ loading: "lazy",
95
+ onError: () => handleImageError(image.id)
96
+ }
97
+ ),
98
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-gradient-to-t from-black/20 to-transparent" })
99
+ ] })
100
+ }
101
+ ),
102
+ showDownload && /* @__PURE__ */ jsx(
103
+ "button",
104
+ {
105
+ type: "button",
106
+ onClick: () => handleDownload(image),
107
+ className: "absolute bottom-3 right-3 flex items-center justify-center bg-black/20 backdrop-blur-sm rounded hover:bg-black/30 transition-colors duration-200 group/button w-6 h-6",
108
+ "aria-label": `Download ${image.title || "imagem"}`,
109
+ children: /* @__PURE__ */ jsx(
110
+ DownloadSimple,
111
+ {
112
+ size: 24,
113
+ weight: "regular",
114
+ className: "text-white group-hover/button:scale-110 transition-transform duration-200"
115
+ }
116
+ )
117
+ }
118
+ )
119
+ ]
120
+ },
121
+ image.id
122
+ )) })
123
+ }
124
+ );
125
+ };
126
+ var Whiteboard_default = Whiteboard;
127
+ export {
128
+ Whiteboard_default as default
129
+ };
130
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/Whiteboard/Whiteboard.tsx","../../src/utils/utils.ts"],"sourcesContent":["import { HTMLAttributes, useCallback, useState } from 'react';\nimport { DownloadSimple } from 'phosphor-react';\nimport { cn } from '../../utils/utils';\n\n// Design constants for critical layout dimensions\nconst IMAGE_WIDTH = 225;\nconst IMAGE_HEIGHT = 90;\n\n/**\n * Whiteboard image item interface\n */\nexport interface WhiteboardImage {\n id: string;\n imageUrl: string;\n title?: string;\n}\n\n/**\n * Whiteboard component props interface\n */\nexport interface WhiteboardProps extends HTMLAttributes<HTMLDivElement> {\n /** Array of images to display in the whiteboard */\n images: WhiteboardImage[];\n /** Whether to show download button on images */\n showDownload?: boolean;\n /** Custom className for the container */\n className?: string;\n /** Callback when download button is clicked */\n onDownload?: (image: WhiteboardImage) => void;\n /** Maximum number of images to display per row on desktop */\n imagesPerRow?: 2 | 3 | 4;\n}\n\n/**\n * Whiteboard component for displaying classroom board images\n * @param props Component properties\n * @returns Whiteboard component\n */\nconst Whiteboard = ({\n images,\n showDownload = true,\n className,\n onDownload,\n imagesPerRow = 2,\n ...rest\n}: WhiteboardProps) => {\n // State to track images that failed to load\n const [imageErrors, setImageErrors] = useState<Set<string>>(new Set());\n\n /**\n * Handle image download\n */\n const handleDownload = useCallback(\n (image: WhiteboardImage) => {\n if (onDownload) {\n onDownload(image);\n } else {\n const link = document.createElement('a');\n link.href = image.imageUrl;\n link.download = image.title || `whiteboard-${image.id}`;\n link.target = '_blank';\n link.rel = 'noopener noreferrer';\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n }\n },\n [onDownload]\n );\n\n /**\n * Handle image loading error\n */\n const handleImageError = useCallback((imageId: string) => {\n setImageErrors((prev) => new Set(prev).add(imageId));\n }, []);\n\n const gridColsClass =\n images?.length === 1\n ? 'grid-cols-1'\n : {\n 2: 'grid-cols-1 sm:grid-cols-2',\n 3: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3',\n 4: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-4',\n }[imagesPerRow];\n\n // Let CSS handle sizing responsively\n\n if (!images || images.length === 0) {\n return (\n <div\n className={cn(\n 'flex items-center justify-center p-8 bg-white border border-gray-100 rounded-xl',\n className\n )}\n {...rest}\n >\n <p className=\"text-gray-400 text-sm\">Nenhuma imagem disponível</p>\n </div>\n );\n }\n\n return (\n <div\n className={cn(\n 'flex flex-col bg-white border border-gray-100 p-4 gap-2 rounded-xl w-fit mx-auto',\n className\n )}\n {...rest}\n >\n <div className={cn('grid gap-4', gridColsClass)}>\n {images.map((image) => (\n <div\n key={image.id}\n className=\"relative group overflow-hidden bg-gray-100 rounded-lg\"\n style={{\n width: `${IMAGE_WIDTH}px`,\n }}\n >\n <div\n className=\"relative\"\n style={{\n width: `${IMAGE_WIDTH}px`,\n height: `${IMAGE_HEIGHT}px`,\n }}\n >\n {imageErrors.has(image.id) ? (\n <div className=\"absolute inset-0 flex items-center justify-center bg-gray-200\">\n <p className=\"text-gray-500 text-sm text-center px-2\">\n Imagem indisponível\n </p>\n </div>\n ) : (\n <>\n <img\n src={image.imageUrl}\n alt={image.title || `Whiteboard ${image.id}`}\n className=\"absolute inset-0 w-full h-full object-cover\"\n loading=\"lazy\"\n onError={() => handleImageError(image.id)}\n />\n <div className=\"absolute inset-0 bg-gradient-to-t from-black/20 to-transparent\" />\n </>\n )}\n </div>\n {showDownload && (\n <button\n type=\"button\"\n onClick={() => handleDownload(image)}\n className=\"absolute bottom-3 right-3 flex items-center justify-center bg-black/20 backdrop-blur-sm rounded hover:bg-black/30 transition-colors duration-200 group/button w-6 h-6\"\n aria-label={`Download ${image.title || 'imagem'}`}\n >\n <DownloadSimple\n size={24}\n weight=\"regular\"\n className=\"text-white group-hover/button:scale-110 transition-transform duration-200\"\n />\n </button>\n )}\n </div>\n ))}\n </div>\n </div>\n );\n};\n\nexport default Whiteboard;\n","import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n"],"mappings":"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","names":[]}
package/dist/index.css CHANGED
@@ -547,6 +547,9 @@
547
547
  .bottom-0 {
548
548
  bottom: calc(var(--spacing) * 0);
549
549
  }
550
+ .bottom-3 {
551
+ bottom: calc(var(--spacing) * 3);
552
+ }
550
553
  .bottom-4 {
551
554
  bottom: calc(var(--spacing) * 4);
552
555
  }
@@ -1681,6 +1684,9 @@
1681
1684
  .border-exam-4 {
1682
1685
  border-color: var(--color-exam-4);
1683
1686
  }
1687
+ .border-gray-100 {
1688
+ border-color: var(--color-gray-100);
1689
+ }
1684
1690
  .border-gray-200 {
1685
1691
  border-color: var(--color-gray-200);
1686
1692
  }
@@ -2047,6 +2053,12 @@
2047
2053
  .bg-background-muted {
2048
2054
  background-color: var(--color-background-muted);
2049
2055
  }
2056
+ .bg-black\/20 {
2057
+ background-color: color-mix(in srgb, #000 20%, transparent);
2058
+ @supports (color: color-mix(in lab, red, red)) {
2059
+ background-color: color-mix(in oklab, var(--color-black) 20%, transparent);
2060
+ }
2061
+ }
2050
2062
  .bg-black\/30 {
2051
2063
  background-color: color-mix(in srgb, #000 30%, transparent);
2052
2064
  @supports (color: color-mix(in lab, red, red)) {
@@ -2173,6 +2185,9 @@
2173
2185
  .bg-gray-100 {
2174
2186
  background-color: var(--color-gray-100);
2175
2187
  }
2188
+ .bg-gray-200 {
2189
+ background-color: var(--color-gray-200);
2190
+ }
2176
2191
  .bg-gray-400 {
2177
2192
  background-color: var(--color-gray-400);
2178
2193
  }
@@ -2563,6 +2578,13 @@
2563
2578
  --tw-gradient-position: to top in oklab;
2564
2579
  background-image: linear-gradient(var(--tw-gradient-stops));
2565
2580
  }
2581
+ .from-black\/20 {
2582
+ --tw-gradient-from: color-mix(in srgb, #000 20%, transparent);
2583
+ @supports (color: color-mix(in lab, red, red)) {
2584
+ --tw-gradient-from: color-mix(in oklab, var(--color-black) 20%, transparent);
2585
+ }
2586
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
2587
+ }
2566
2588
  .from-black\/70 {
2567
2589
  --tw-gradient-from: color-mix(in srgb, #000 70%, transparent);
2568
2590
  @supports (color: color-mix(in lab, red, red)) {
@@ -3730,6 +3752,16 @@
3730
3752
  }
3731
3753
  }
3732
3754
  }
3755
+ .group-hover\/button\:scale-110 {
3756
+ &:is(:where(.group\/button):hover *) {
3757
+ @media (hover: hover) {
3758
+ --tw-scale-x: 110%;
3759
+ --tw-scale-y: 110%;
3760
+ --tw-scale-z: 110%;
3761
+ scale: var(--tw-scale-x) var(--tw-scale-y);
3762
+ }
3763
+ }
3764
+ }
3733
3765
  .placeholder\:text-text-600 {
3734
3766
  &::-moz-placeholder {
3735
3767
  color: var(--color-text-600);
@@ -4616,6 +4648,16 @@
4616
4648
  }
4617
4649
  }
4618
4650
  }
4651
+ .hover\:bg-black\/30 {
4652
+ &:hover {
4653
+ @media (hover: hover) {
4654
+ background-color: color-mix(in srgb, #000 30%, transparent);
4655
+ @supports (color: color-mix(in lab, red, red)) {
4656
+ background-color: color-mix(in oklab, var(--color-black) 30%, transparent);
4657
+ }
4658
+ }
4659
+ }
4660
+ }
4619
4661
  .hover\:bg-blue-600 {
4620
4662
  &:hover {
4621
4663
  @media (hover: hover) {
@@ -8176,6 +8218,11 @@
8176
8218
  max-width: 100px;
8177
8219
  }
8178
8220
  }
8221
+ .sm\:grid-cols-2 {
8222
+ @media (width >= 40rem) {
8223
+ grid-template-columns: repeat(2, minmax(0, 1fr));
8224
+ }
8225
+ }
8179
8226
  .sm\:flex-row {
8180
8227
  @media (width >= 40rem) {
8181
8228
  flex-direction: row;