diff --git a/.github/workflows/oxlint.yml b/.github/workflows/eslint.yml similarity index 90% rename from .github/workflows/oxlint.yml rename to .github/workflows/eslint.yml index 117ef4a6..a7200e7a 100644 --- a/.github/workflows/oxlint.yml +++ b/.github/workflows/eslint.yml @@ -12,5 +12,5 @@ jobs: cd frontend && \ corepack install -g yarn && \ yarn && \ - yarn lint-staged && \ + yarn lint && \ yarn build diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs index 4fc097f8..a65e2a6f 100644 --- a/frontend/eslint.config.mjs +++ b/frontend/eslint.config.mjs @@ -5,50 +5,52 @@ import react from 'eslint-plugin-react'; import reactCompiler from 'eslint-plugin-react-compiler'; export default tseslint.config( - { - ignores: [ - 'eslint.config.mjs', - 'tailwind.config.js', - 'postcss.config.cjs', - 'vite.config.ts', - 'dist/**' - ] - }, - { - languageOptions: { - parserOptions: { - projectService: true, - tsconfigRootDir: import.meta.dirname - } - } - }, - eslint.configs.recommended, - ...tseslint.configs.recommendedTypeChecked, - ...tseslint.configs.stylisticTypeChecked, - // These @ts-ignores should go away when https://github.com/jsx-eslint/eslint-plugin-react/issues/3838 is solved - // @ts-ignore - { - // @ts-ignore - ...react.configs.flat.recommended, - plugins: { - // @ts-ignore - react, - // React compiler brings rules for react that are going to be solved when the compiler is launched, but is the React recommended way of good practices. - // See: https://react.dev/learn/react-compiler - 'react-compiler': reactCompiler - }, - settings: { - react: { - version: 'detect' - } - }, - // @ts-ignore - React ESLint seems to have issues with typescript-eslint - rules: { - ...react.configs.flat.recommended.rules, - 'react/react-in-jsx-scope': 'off', - // @ts-ignore - 'react/jsx-filename-extension': [1, { extensions: ['.tsx'] }], - 'react-compiler/react-compiler': 'warn' - } - } + { + ignores: [ + 'eslint.config.mjs', + 'tailwind.config.js', + 'postcss.config.cjs', + 'vite.config.ts', + 'dist/**', + 'node_modules/**' + ] + }, + { + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname + } + } + }, + eslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + ...tseslint.configs.stylisticTypeChecked, + // These @ts-ignores should go away when https://github.com/jsx-eslint/eslint-plugin-react/issues/3838 is solved + // @ts-ignore + { + // @ts-ignore + ...react.configs.flat.recommended, + plugins: { + // @ts-ignore + react, + // React compiler brings rules for react that are going to be solved when the compiler is launched, but is the React recommended way of good practices. + // See: https://react.dev/learn/react-compiler + 'react-compiler': reactCompiler + }, + settings: { + react: { + version: 'detect' + } + }, + // @ts-ignore - React ESLint seems to have issues with typescript-eslint + rules: { + ...react.configs.flat.recommended.rules, + 'react/react-in-jsx-scope': 'off', + "react/prop-types": "off", + // @ts-ignore + 'react/jsx-filename-extension': [1, { extensions: ['.tsx'] }], + 'react-compiler/react-compiler': 'warn' + } + } ); diff --git a/frontend/package.json b/frontend/package.json index 3c091188..10a9a011 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,7 +6,7 @@ "scripts": { "dev": "vite", "build": "tsc -b && vite build", - "lint-staged": "oxlint src && eslint --fix src", + "lint-staged": "eslint --fix src", "lint": "eslint .", "preview": "vite preview", "prepare": "cd .. && husky frontend/.husky" @@ -17,7 +17,6 @@ "axios": "^1.6.8", "daisyui": "^4.10.5", "eslint-plugin-prettier": "^5.2.1", - "oxlint": "^0.3.2", "postcss": "^8.4.38", "prettier": "^3.2.5", "react": "^18.2.0", diff --git a/frontend/src/AuthContext.tsx b/frontend/src/AuthContext.tsx index d09fa94b..388c9a22 100644 --- a/frontend/src/AuthContext.tsx +++ b/frontend/src/AuthContext.tsx @@ -3,10 +3,12 @@ import React from 'react'; import { useState, useEffect } from 'react'; import { INIT_KRATOS_LOGIN_FLOW, User } from '@/common'; import { AuthContext, fetchUser } from '@/useAuth'; +import { useNavigate } from 'react-router-dom'; export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const navigate = useNavigate(); const passReset = '/reset-password'; const [user, setUser] = useState(); const [loading, setLoading] = useState(true); @@ -24,14 +26,14 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ return
; } if (!user && !loading) { - window.location.href = INIT_KRATOS_LOGIN_FLOW; + navigate(INIT_KRATOS_LOGIN_FLOW); return null; } else if ( !loading && user?.password_reset && window.location.pathname !== passReset ) { - window.location.href = passReset; + navigate(passReset); return null; } return ( diff --git a/frontend/src/Components/CategoryItem.tsx b/frontend/src/Components/CategoryItem.tsx index b09f7b94..03771a56 100644 --- a/frontend/src/Components/CategoryItem.tsx +++ b/frontend/src/Components/CategoryItem.tsx @@ -120,37 +120,33 @@ export default function CategoryItem({ {/* Modals */} - {activeLinkToDelete && ( - setActiveLinkToDelete(undefined)} - onSuccess={() => - deleteLink(category, activeLinkToDelete) - } - /> - } - ref={deleteLinkModal} - /> - )} - {addLinkModal.current && ( - { - addLink(category, title, url); - addLinkModal.current?.close(); - }} - /> - } - ref={addLinkModal} - /> - )} + setActiveLinkToDelete(undefined)} + onSuccess={() => + deleteLink(category, activeLinkToDelete!) + } + /> + } + ref={deleteLinkModal} + /> + { + addLink(category, title, url); + addLinkModal.current?.close(); + }} + /> + } + ref={addLinkModal} + /> ); } diff --git a/frontend/src/Components/LibraryLayout.tsx b/frontend/src/Components/LibraryLayout.tsx index 82444886..80e57202 100644 --- a/frontend/src/Components/LibraryLayout.tsx +++ b/frontend/src/Components/LibraryLayout.tsx @@ -26,14 +26,14 @@ export default function LibaryLayout({ studentView?: boolean; }) { const { user } = useAuth(); - if (!user) { - return null; - } const [searchTerm, setSearchTerm] = useState(''); + const [perPage, setPerPage] = useState(20); + const [pageQuery, setPageQuery] = useState(1); const allLibrariesTab: Tab = { name: 'All', value: 'all' }; + let role = user?.role ?? UserRole.Student; const [activeTab, setActiveTab] = useState(allLibrariesTab); const [filterLibraries, setFilterLibraries] = useState( FilterLibraries['All Libraries'] @@ -41,12 +41,6 @@ export default function LibaryLayout({ const [filterLibrariesAdmin, setFilterLibrariesAdmin] = useState( FilterLibrariesAdmin['All Libraries'] ); - let role = user.role; - if (studentView) { - role = UserRole.Student; - } - const [perPage, setPerPage] = useState(20); - const [pageQuery, setPageQuery] = useState(1); const { data: libraries, mutate: mutateLibraries, @@ -55,21 +49,8 @@ export default function LibaryLayout({ } = useSWR, AxiosError>( `/api/libraries?page=${pageQuery}&per_page=${perPage}&visibility=${role == UserRole.Admin ? filterLibrariesAdmin : studentView ? 'visible' : filterLibraries}&search=${searchTerm}` ); - const librariesMeta = libraries?.meta ?? { - total: 0, - per_page: 20, - page: 1, - current_page: 1, - last_page: 1 - }; - const { data: openContentProviders } = useSWR>('/api/open-content'); - const handleSetPerPage = (perPage: number) => { - setPerPage(perPage); - setPageQuery(1); - void mutateLibraries(); - }; const openContentTabs = useMemo(() => { return [ allLibrariesTab, @@ -85,6 +66,25 @@ export default function LibaryLayout({ useEffect(() => { setPageQuery(1); }, [filterLibrariesAdmin, filterLibraries, searchTerm]); + if (studentView) { + role = UserRole.Student; + } + if (!user) { + return null; + } + const librariesMeta = libraries?.meta ?? { + total: 0, + per_page: 20, + page: 1, + current_page: 1, + last_page: 1 + }; + + const handleSetPerPage = (perPage: number) => { + setPerPage(perPage); + setPageQuery(1); + void mutateLibraries(); + }; return (
diff --git a/frontend/src/Components/MilestonesBarChart.tsx b/frontend/src/Components/MilestonesBarChart.tsx index 654ea38f..4d7bef68 100644 --- a/frontend/src/Components/MilestonesBarChart.tsx +++ b/frontend/src/Components/MilestonesBarChart.tsx @@ -13,8 +13,7 @@ import { useContext } from 'react'; import { CourseMilestones, YAxisTickProps } from '@/common'; const maxYAxisLabel = (props: YAxisTickProps) => { - const { theme } = useContext(ThemeContext); - const fill = theme == 'light' ? '#666' : '#CCC'; + const fill = props.theme == 'light' ? '#666' : '#CCC'; const { x, y, payload } = props; const name = payload.value; diff --git a/frontend/src/Components/forms/ChangePasswordForm.tsx b/frontend/src/Components/forms/ChangePasswordForm.tsx index 91a482cc..c9a53853 100644 --- a/frontend/src/Components/forms/ChangePasswordForm.tsx +++ b/frontend/src/Components/forms/ChangePasswordForm.tsx @@ -4,9 +4,15 @@ import InputError from '../../Components/inputs/InputError'; import PrimaryButton from '../../Components/PrimaryButton'; import { TextInput } from '../../Components/inputs/TextInput'; import { CheckIcon, XMarkIcon } from '@heroicons/react/24/solid'; -import { useAuth } from '@/useAuth'; import API from '@/api/api'; -import { AuthResponse, Facility, ServerResponseOne, UserRole } from '@/common'; +import { + AuthResponse, + DEFAULT_ADMIN_FACILITY, + DEFAULT_ADMIN_ID, + ServerResponseOne, + User, + UserRole +} from '@/common'; import { useLoaderData } from 'react-router-dom'; interface Inputs { password: string; @@ -17,8 +23,7 @@ interface Inputs { export default function ChangePasswordForm() { const [errorMessage, setErrorMessage] = useState(''); const [processing, setProcessing] = useState(false); - const loaderData = useLoaderData() as Facility | null; - const { user } = useAuth(); + const user = useLoaderData() as User; const { control, @@ -50,9 +55,9 @@ export default function ChangePasswordForm() { const validFacility = facility && facility.length > 2 && facility.trim().length > 2; const isFirstAdminLogin = - user?.id === 1 && - user?.role === UserRole.Admin && - loaderData?.name === 'Default'; + user.id === DEFAULT_ADMIN_ID && + user.role === UserRole.Admin && + user.facility_name === DEFAULT_ADMIN_FACILITY; const submit: SubmitHandler = async (data) => { setErrorMessage(''); diff --git a/frontend/src/Components/forms/MapUserForm.tsx b/frontend/src/Components/forms/MapUserForm.tsx index 24154a04..e1057b8c 100644 --- a/frontend/src/Components/forms/MapUserForm.tsx +++ b/frontend/src/Components/forms/MapUserForm.tsx @@ -122,8 +122,8 @@ export default function MapUserForm({ ) : fuzzySearchUsers?.length != 0 && !seeAllUsers ? ( <>

- We have found a potential match to the student you'd - like to map: + We have found a potential match to the student + you&aposd like to map:

{fuzzySearchUsers?.map((user: User) => { return ( @@ -168,10 +168,9 @@ export default function MapUserForm({ )} {unmappedUsers?.map((user: User) => { return ( - +

+ +

); })} {meta && ( diff --git a/frontend/src/Components/forms/RegisterOidcClientForm.tsx b/frontend/src/Components/forms/RegisterOidcClientForm.tsx index 11c8a520..49d9e292 100644 --- a/frontend/src/Components/forms/RegisterOidcClientForm.tsx +++ b/frontend/src/Components/forms/RegisterOidcClientForm.tsx @@ -66,7 +66,7 @@ export default function RegisterOidcClientForm({
If you do not choose to auto register, you must manually setup authentication for UnlockEd the provider - platform's settings. + platform&aposs settings.
)}