move workflow files - add prettier formatting and format

This commit is contained in:
CarterPerez-dev 2025-11-12 14:57:47 -05:00
parent d1bf2cf2a9
commit 0c4a22748c
23 changed files with 771 additions and 121 deletions

120
.github/workflows/eslint-check.yml vendored Normal file
View File

@ -0,0 +1,120 @@
name: ESLint Check
on:
pull_request:
branches: [ '*' ]
paths:
- 'PROJECTS/api-security-scanner/frontend/**/*.ts'
- 'PROJECTS/api-security-scanner/frontend/**/*.tsx'
- 'PROJECTS/api-security-scanner/frontend/eslint.config.js'
- 'PROJECTS/api-security-scanner/frontend/tsconfig*.json'
- 'PROJECTS/api-security-scanner/frontend/package.json'
- '.github/workflows/eslint-check.yml'
jobs:
eslint-check:
name: ESLint TypeScript Check
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
defaults:
run:
working-directory: PROJECTS/api-security-scanner/frontend
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: PROJECTS/api-security-scanner/frontend/package-lock.json
- name: Install dependencies
run: npm install
- name: Run ESLint
id: eslint_check
run: |
echo "Running ESLint on TypeScript/React files..."
if npm run lint:eslint > eslint-output.txt 2>&1; then
echo "ESLINT_PASSED=true" >> $GITHUB_ENV
echo "✅ No ESLint errors found!"
echo "ERROR_COUNT=0" >> $GITHUB_ENV
else
echo "ESLINT_PASSED=false" >> $GITHUB_ENV
# Count error lines (lines that contain file paths with problems)
error_count=$(grep -c "^/" eslint-output.txt || echo "0")
echo "ERROR_COUNT=$error_count" >> $GITHUB_ENV
echo "⚠️ ESLint found issues in $error_count files!"
fi
cat eslint-output.txt
continue-on-error: true
- name: Create ESLint Summary
id: create_summary
if: github.event_name == 'pull_request'
run: |
{
echo '## 🔍 ESLint Results'
echo ''
if [[ "${{ env.ESLINT_PASSED }}" == "true" ]]; then
echo '### ✅ **Perfect! No ESLint issues found** 🎉'
echo ''
echo 'Your TypeScript and React code follows all the coding standards perfectly!'
echo ''
echo '**What was checked:**'
echo '- TypeScript strict type checking and stylistic rules'
echo '- React component patterns and hooks usage'
echo '- Code complexity, naming conventions, and best practices'
echo '- Accessibility (jsx-a11y) and React Refresh compatibility'
else
echo '### ❌ **ESLint found issues in ${{ env.ERROR_COUNT }} files**'
echo ''
echo 'Please review and fix the TypeScript/React issues below:'
echo ''
echo '<details><summary>📋 View detailed ESLint output</summary>'
echo ''
echo '```'
head -100 eslint-output.txt
echo '```'
echo '</details>'
echo ''
echo '**How to fix:**'
echo '1. Run `cd frontend && npm run lint:eslint` locally to see the issues'
echo '2. Fix the reported TypeScript, React, and code quality problems'
echo '3. For auto-fixable issues: `cd frontend && npx eslint . --ext .ts,.tsx --fix`'
echo '4. Push your changes to update this PR'
fi
echo ''
echo '**Commands:**'
echo '- `cd frontend && npm run lint:eslint` - Run ESLint'
echo '- `cd frontend && npx eslint . --ext .ts,.tsx --fix` - Auto-fix issues'
echo '- ESLint config: `frontend/eslint.config.js`'
echo ''
echo '<!-- eslint-check-comment-marker -->'
} > eslint-report.md
- name: Post PR Comment
if: github.event_name == 'pull_request'
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ github.event.pull_request.number }}
body-path: PROJECTS/api-security-scanner/frontend/eslint-report.md
edit-mode: replace
- name: Exit with proper code
run: |
if [[ "${{ env.ESLINT_PASSED }}" == "false" ]]; then
echo "❌ ESLint checks failed. Please fix the issues above."
exit 1
else
echo "✅ All ESLint checks passed!"
exit 0
fi

159
.github/workflows/lint.yml vendored Executable file
View File

@ -0,0 +1,159 @@
name: Lint & Type Check
on:
push:
branches: [ 'main' ]
pull_request:
branches: [ '*' ]
workflow_dispatch:
jobs:
lint:
name: Run Linters
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
defaults:
run:
working-directory: PROJECTS/api-security-scanner/backend
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Cache pip dependencies
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('PROJECTS/api-security-scanner/backend/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
- name: Run pylint
id: pylint
run: |
echo "Running pylint..."
if pylint . > pylint-output.txt 2>&1; then
echo "PYLINT_PASSED=true" >> $GITHUB_ENV
echo "✅ No pylint errors found!"
else
echo "PYLINT_PASSED=false" >> $GITHUB_ENV
echo "⚠️ Pylint found issues!"
fi
cat pylint-output.txt
continue-on-error: true
- name: Run ruff
id: ruff
run: |
echo "Running ruff check..."
if ruff check . > ruff-output.txt 2>&1; then
echo "RUFF_PASSED=true" >> $GITHUB_ENV
echo "✅ No ruff errors found!"
else
echo "RUFF_PASSED=false" >> $GITHUB_ENV
echo "⚠️ Ruff found issues"
fi
cat ruff-output.txt
continue-on-error: true
- name: Run mypy
id: mypy
run: |
echo "Running mypy..."
if mypy . > mypy-output.txt 2>&1; then
echo "MYPY_PASSED=true" >> $GITHUB_ENV
echo "✅ No mypy errors found"
else
echo "MYPY_PASSED=false" >> $GITHUB_ENV
echo "⚠️ Mypy found issues"
fi
cat mypy-output.txt
continue-on-error: true
- name: Create Lint Summary
id: create_summary
if: github.event_name == 'pull_request'
run: |
{
echo '## 🔍 Lint & Type Check Results'
echo ''
# Pylint Status
if [[ "${{ env.PYLINT_PASSED }}" == "true" ]]; then
echo '### ✅ Pylint: **Passed**'
echo 'No pylint issues found.'
else
echo '### ⚠️ Pylint: **Issues Found**'
echo '<details><summary>View pylint output</summary>'
echo ''
echo '```'
head -100 pylint-output.txt
echo '```'
echo '</details>'
fi
echo ''
# Ruff Status
if [[ "${{ env.RUFF_PASSED }}" == "true" ]]; then
echo '### ✅ Ruff: **Passed**'
echo 'No ruff issues found.'
else
echo '### ⚠️ Ruff: **Issues Found**'
echo '<details><summary>View ruff output</summary>'
echo ''
echo '```'
head -100 ruff-output.txt
echo '```'
echo '</details>'
fi
echo ''
# Mypy Status
if [[ "${{ env.MYPY_PASSED }}" == "true" ]]; then
echo '### ✅ Mypy: **Passed**'
echo 'No mypy issues found.'
else
echo '### ⚠️ Mypy: **Issues Found**'
echo '<details><summary>View mypy output</summary>'
echo ''
echo '```'
head -100 mypy-output.txt
echo '```'
echo '</details>'
fi
echo ''
# Overall Summary
if [[ "${{ env.PYLINT_PASSED }}" == "true" ]] && [[ "${{ env.RUFF_PASSED }}" == "true" ]] && [[ "${{ env.MYPY_PASSED }}" == "true" ]]; then
echo '---'
echo '### All checks passed!'
else
echo '---'
echo '### Review the issues above and consider fixing them.'
fi
echo ''
echo '<!-- lint-check-comment-marker -->'
} > ../lint-report.md
- name: Post PR Comment
if: github.event_name == 'pull_request'
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ github.event.pull_request.number }}
body-path: PROJECTS/api-security-scanner/lint-report.md
comment-marker: lint-check-comment-marker

129
.github/workflows/typescript-check.yml vendored Normal file
View File

@ -0,0 +1,129 @@
name: TypeScript Type Check
on:
pull_request:
branches: [ '*' ]
paths:
- 'PROJECTS/api-security-scanner/frontend/**/*.ts'
- 'PROJECTS/api-security-scanner/frontend/**/*.tsx'
- 'PROJECTS/api-security-scanner/frontend/tsconfig*.json'
- 'PROJECTS/api-security-scanner/frontend/package.json'
- '.github/workflows/typescript-check.yml'
jobs:
typescript-check:
name: TypeScript Type Check
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
defaults:
run:
working-directory: PROJECTS/api-security-scanner/frontend
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: PROJECTS/api-security-scanner/frontend/package-lock.json
- name: Install dependencies
run: |
for i in {1..3}; do
echo "Attempt $i of 3..."
if npm install; then
echo "✅ npm install succeeded"
break
else
echo "⚠️ npm install failed, retrying in 10 seconds..."
sleep 10
fi
done
- name: Run TypeScript type checking
id: typescript_check
run: |
echo "Running TypeScript type checking..."
if npm run lint:types > typescript-output.txt 2>&1; then
echo "TYPESCRIPT_PASSED=true" >> $GITHUB_ENV
echo "✅ No TypeScript type errors found!"
echo "ERROR_COUNT=0" >> $GITHUB_ENV
else
echo "TYPESCRIPT_PASSED=false" >> $GITHUB_ENV
# Count error lines (lines that contain errors)
error_count=$(grep -c "error TS" typescript-output.txt || echo "0")
echo "ERROR_COUNT=$error_count" >> $GITHUB_ENV
echo "⚠️ TypeScript found $error_count type errors!"
fi
cat typescript-output.txt
continue-on-error: true
- name: Create TypeScript Summary
id: create_summary
if: github.event_name == 'pull_request'
run: |
{
echo '## 📝 TypeScript Type Check Results'
echo ''
if [[ "${{ env.TYPESCRIPT_PASSED }}" == "true" ]]; then
echo '### ✅ **Perfect! No TypeScript type errors found** 🎉'
echo ''
echo 'Your TypeScript code passes all strict type checking requirements!'
echo ''
echo '**What was checked:**'
echo '- Strict type checking with `exactOptionalPropertyTypes`'
echo '- No unused locals or parameters'
echo '- Proper return types and void expressions'
echo '- Module resolution and import/export syntax'
else
echo '### ❌ **TypeScript found ${{ env.ERROR_COUNT }} type errors**'
echo ''
echo 'Please review and fix the TypeScript type errors below:'
echo ''
echo '<details><summary>📋 View detailed TypeScript output</summary>'
echo ''
echo '```'
head -100 typescript-output.txt
echo '```'
echo '</details>'
echo ''
echo '**How to fix:**'
echo '1. Run `cd frontend && npm run lint:types` locally to see the type errors'
echo '2. Fix the reported TypeScript type issues'
echo '3. Ensure all variables have proper types and return types are explicit'
echo '4. Push your changes to update this PR'
fi
echo ''
echo '**Commands:**'
echo '- `cd frontend && npm run lint:types` - Run TypeScript type checking'
echo '- `cd frontend && npm run build` - Run full build with type checking'
echo '- TypeScript config: `frontend/tsconfig.app.json`'
echo ''
echo '<!-- typescript-check-comment-marker -->'
} > typescript-report.md
- name: Post PR Comment
if: github.event_name == 'pull_request'
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ github.event.pull_request.number }}
body-path: PROJECTS/api-security-scanner/frontend/typescript-report.md
edit-mode: replace
- name: Exit with proper code
run: |
if [[ "${{ env.TYPESCRIPT_PASSED }}" == "false" ]]; then
echo "❌ TypeScript type checking failed. Please fix the type errors above."
exit 1
else
echo "✅ All TypeScript type checks passed!"
exit 0
fi

View File

@ -0,0 +1,24 @@
# Dependencies
node_modules/
# Build outputs
dist/
build/
# Logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Coverage directory used by tools like istanbul
coverage/
# Other
.DS_Store
.env*
.vscode/
.idea/

View File

@ -0,0 +1,76 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"printWidth": 77,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"quoteProps": "consistent",
"jsxSingleQuote": false,
"trailingComma": "all",
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "always",
"proseWrap": "always",
"htmlWhitespaceSensitivity": "strict",
"endOfLine": "lf",
"embeddedLanguageFormatting": "auto",
"singleAttributePerLine": true,
"overrides": [
{
"files": "*.tsx",
"options": {
"printWidth": 77,
"jsxSingleQuote": false,
"bracketSameLine": false
}
},
{
"files": "*.ts",
"options": {
"printWidth": 77,
"parser": "typescript"
}
},
{
"files": ["*.json", "*.jsonc"],
"options": {
"printWidth": 80,
"trailingComma": "none"
}
},
{
"files": "*.scss",
"options": {
"singleQuote": false,
"printWidth": 100
}
},
{
"files": "*.md",
"options": {
"proseWrap": "always",
"printWidth": 80
}
},
{
"files": ["*.yml", "*.yaml"],
"options": {
"singleQuote": false,
"bracketSpacing": true
}
},
{
"files": ".prettierrc",
"options": {
"parser": "json"
}
},
{
"files": "package.json",
"options": {
"printWidth": 1000
}
}
]
}

View File

@ -13,7 +13,9 @@ import { router } from '@/router';
import { useAuthStore } from '@/store/authStore';
const AuthInitializer = (): null => {
const loadUserFromStorage = useAuthStore((state) => state.loadUserFromStorage);
const loadUserFromStorage = useAuthStore(
(state) => state.loadUserFromStorage,
);
useEffect(() => {
loadUserFromStorage();
@ -27,7 +29,12 @@ function App(): React.ReactElement {
<QueryClientProvider client={queryClient}>
<AuthInitializer />
<RouterProvider router={router} />
<Toaster position="top-right" closeButton duration={4500} theme="dark" />
<Toaster
position="top-right"
closeButton
duration={4500}
theme="dark"
/>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);

View File

@ -21,9 +21,10 @@ export const LoginForm = (): React.ReactElement => {
const [email, setEmail] = useState<string>('');
const [password, setPassword] = useState<string>('');
const [errors, setErrors] = useState<{ email?: string; password?: string }>(
{},
);
const [errors, setErrors] = useState<{
email?: string;
password?: string;
}>({});
const { mutate: login, isPending, error } = useLogin();
@ -49,14 +50,19 @@ export const LoginForm = (): React.ReactElement => {
}
};
const validateField = (field: 'email' | 'password', value: string): void => {
const validateField = (
field: 'email' | 'password',
value: string,
): void => {
const result = loginSchema.safeParse({
email: field === 'email' ? value : email,
password: field === 'password' ? value : password,
});
if (!result.success) {
const fieldError = result.error.issues.find((err) => err.path[0] === field);
const fieldError = result.error.issues.find(
(err) => err.path[0] === field,
);
if (fieldError !== null && fieldError !== undefined) {
setErrors((prev) => ({ ...prev, [field]: fieldError.message }));
} else {
@ -120,7 +126,10 @@ export const LoginForm = (): React.ReactElement => {
};
return (
<form className="auth-form" onSubmit={handleSubmit}>
<form
className="auth-form"
onSubmit={handleSubmit}
>
<div className="auth-form__header">
<h1 className="auth-form__title">Welcome Back</h1>
<p className="auth-form__subtitle">Sign in to your account</p>
@ -153,18 +162,29 @@ export const LoginForm = (): React.ReactElement => {
</div>
{error !== null && error !== undefined && isAxiosError(error) ? (
<div className="auth-form__error-message" role="alert">
{(error.response?.data as { detail?: string } | undefined)?.detail ?? 'Login failed. Please try again.'}
<div
className="auth-form__error-message"
role="alert"
>
{(error.response?.data as { detail?: string } | undefined)
?.detail ?? 'Login failed. Please try again.'}
</div>
) : null}
<Button type="submit" isLoading={isPending} disabled={isPending}>
<Button
type="submit"
isLoading={isPending}
disabled={isPending}
>
Sign In
</Button>
<p className="auth-form__link">
Don&apos;t have an account?{' '}
<Link to="/register" className="auth-form__link-text">
<Link
to="/register"
className="auth-form__link-text"
>
Sign up
</Link>
</p>

View File

@ -15,7 +15,9 @@ import './AuthForm.css';
export const RegisterForm = (): React.ReactElement => {
const registerFormState = useUIStore((state) => state.registerForm);
const setRegisterFormField = useUIStore((state) => state.setRegisterFormField);
const setRegisterFormField = useUIStore(
(state) => state.setRegisterFormField,
);
const clearRegisterForm = useUIStore((state) => state.clearRegisterForm);
const clearExpiredData = useUIStore((state) => state.clearExpiredData);
@ -61,7 +63,10 @@ export const RegisterForm = (): React.ReactElement => {
const handleConfirmPasswordChange = (value: string): void => {
setConfirmPassword(value);
setRegisterFormField('confirmPassword', value);
if (errors.confirmPassword !== null && errors.confirmPassword !== undefined) {
if (
errors.confirmPassword !== null &&
errors.confirmPassword !== undefined
) {
validateField('confirmPassword', value);
}
};
@ -77,7 +82,9 @@ export const RegisterForm = (): React.ReactElement => {
});
if (!result.success) {
const fieldError = result.error.issues.find((err) => err.path[0] === field);
const fieldError = result.error.issues.find(
(err) => err.path[0] === field,
);
if (fieldError !== null && fieldError !== undefined) {
setErrors((prev) => ({ ...prev, [field]: fieldError.message }));
} else {
@ -94,7 +101,9 @@ export const RegisterForm = (): React.ReactElement => {
}
};
const handleBlur = (field: 'email' | 'password' | 'confirmPassword'): void => {
const handleBlur = (
field: 'email' | 'password' | 'confirmPassword',
): void => {
let value: string;
if (field === 'email') {
value = email;
@ -153,10 +162,15 @@ export const RegisterForm = (): React.ReactElement => {
};
return (
<form className="auth-form" onSubmit={handleSubmit}>
<form
className="auth-form"
onSubmit={handleSubmit}
>
<div className="auth-form__header">
<h1 className="auth-form__title">Create Account</h1>
<p className="auth-form__subtitle">Get started with API Security Scanner</p>
<p className="auth-form__subtitle">
Get started with API Security Scanner
</p>
</div>
<div className="auth-form__fields">
@ -198,18 +212,29 @@ export const RegisterForm = (): React.ReactElement => {
</div>
{error !== null && error !== undefined && isAxiosError(error) ? (
<div className="auth-form__error-message" role="alert">
{(error.response?.data as { detail?: string } | undefined)?.detail ?? 'Registration failed. Please try again.'}
<div
className="auth-form__error-message"
role="alert"
>
{(error.response?.data as { detail?: string } | undefined)
?.detail ?? 'Registration failed. Please try again.'}
</div>
) : null}
<Button type="submit" isLoading={isPending} disabled={isPending}>
<Button
type="submit"
isLoading={isPending}
disabled={isPending}
>
Create Account
</Button>
<p className="auth-form__link">
Already have an account?{' '}
<Link to="/login" className="auth-form__link-text">
<Link
to="/login"
className="auth-form__link-text"
>
Sign in
</Link>
</p>

View File

@ -13,12 +13,16 @@ interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
export const Input = forwardRef<HTMLInputElement, InputProps>(
({ label, error, id, ...props }, ref) => {
const inputId = id ?? `input-${label.toLowerCase().replace(/\s+/g, '-')}`;
const inputId =
id ?? `input-${label.toLowerCase().replace(/\s+/g, '-')}`;
const errorId = `${inputId}-error`;
return (
<div className="input-wrapper">
<label htmlFor={inputId} className="input-label">
<label
htmlFor={inputId}
className="input-label"
>
{label}
</label>
<input
@ -26,11 +30,17 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
id={inputId}
className={`input ${error !== null && error !== undefined ? 'input--error' : ''}`}
aria-invalid={error !== null && error !== undefined ? true : false}
aria-describedby={error !== null && error !== undefined ? errorId : undefined}
aria-describedby={
error !== null && error !== undefined ? errorId : undefined
}
{...props}
/>
{error !== null && error !== undefined ? (
<p id={errorId} className="input-error" role="alert">
<p
id={errorId}
className="input-error"
role="alert"
>
{error}
</p>
) : null}

View File

@ -14,18 +14,26 @@ export const LoadingOverlay = ({
tests,
}: LoadingOverlayProps): React.ReactElement => {
return (
<div className="loading-overlay" role="dialog" aria-label="Scan in progress">
<div
className="loading-overlay"
role="dialog"
aria-label="Scan in progress"
>
<div className="loading-overlay__content">
<div className="loading-overlay__spinner">
<div className="spinner"></div>
</div>
<h2 className="loading-overlay__title">Running Security Scan</h2>
<p className="loading-overlay__subtitle">
Testing {tests.length} {tests.length === 1 ? 'vulnerability' : 'vulnerabilities'}
Testing {tests.length}{' '}
{tests.length === 1 ? 'vulnerability' : 'vulnerabilities'}
</p>
<div className="loading-overlay__tests">
{tests.map((test) => (
<div key={test} className="loading-overlay__test">
<div
key={test}
className="loading-overlay__test"
>
{TEST_TYPE_LABELS[test]}
</div>
))}

View File

@ -33,7 +33,12 @@ export const ProtectedRoute = ({
}
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
return (
<Navigate
to="/login"
replace
/>
);
}
return children as React.ReactElement;

View File

@ -138,68 +138,82 @@ export const NewScanForm = (): React.ReactElement => {
return (
<>
{isPending ? <LoadingOverlay tests={selectedTests} /> : null}
<form className="scan-form" onSubmit={handleSubmit}>
<div className="scan-form__fields">
<Input
label="Target URL"
type="url"
value={targetUrl}
onChange={(e) => handleTargetUrlChange(e.target.value)}
error={errors.targetUrl}
placeholder="https://api.example.com/endpoint"
required
/>
<form
className="scan-form"
onSubmit={handleSubmit}
>
<div className="scan-form__fields">
<Input
label="Target URL"
type="url"
value={targetUrl}
onChange={(e) => handleTargetUrlChange(e.target.value)}
error={errors.targetUrl}
placeholder="https://api.example.com/endpoint"
required
/>
<Input
label="Auth Token (Optional)"
type="text"
value={authToken}
onChange={(e) => handleAuthTokenChange(e.target.value)}
error={errors.authToken}
placeholder="Bearer token or API key"
/>
<Input
label="Auth Token (Optional)"
type="text"
value={authToken}
onChange={(e) => handleAuthTokenChange(e.target.value)}
error={errors.authToken}
placeholder="Bearer token or API key"
/>
<div className="scan-form__field">
<label className="scan-form__label">
Select Tests
{errors.testsToRun !== null && errors.testsToRun !== undefined ? (
<span className="scan-form__error" role="alert">
{errors.testsToRun}
</span>
) : null}
</label>
<div className="scan-form__checkboxes">
{Object.values(SCAN_TEST_TYPES).map((test) => (
<label key={test} className="scan-form__checkbox-label">
<input
type="checkbox"
checked={selectedTests.includes(test)}
onChange={() => handleTestToggle(test)}
className="scan-form__checkbox"
/>
<span>{TEST_TYPE_LABELS[test]}</span>
</label>
))}
<div className="scan-form__field">
<label className="scan-form__label">
Select Tests
{errors.testsToRun !== null &&
errors.testsToRun !== undefined ? (
<span
className="scan-form__error"
role="alert"
>
{errors.testsToRun}
</span>
) : null}
</label>
<div className="scan-form__checkboxes">
{Object.values(SCAN_TEST_TYPES).map((test) => (
<label
key={test}
className="scan-form__checkbox-label"
>
<input
type="checkbox"
checked={selectedTests.includes(test)}
onChange={() => handleTestToggle(test)}
className="scan-form__checkbox"
/>
<span>{TEST_TYPE_LABELS[test]}</span>
</label>
))}
</div>
</div>
<Input
label="Max Requests"
type="number"
value={maxRequests}
onChange={(e) => handleMaxRequestsChange(e.target.value)}
error={errors.maxRequests}
placeholder="50"
min="1"
max="50"
required
/>
</div>
<Input
label="Max Requests"
type="number"
value={maxRequests}
onChange={(e) => handleMaxRequestsChange(e.target.value)}
error={errors.maxRequests}
placeholder="50"
min="1"
max="50"
required
/>
</div>
<Button type="submit" isLoading={isPending} disabled={isPending}>
{isPending ? 'Running Scan...' : 'Start Scan'}
</Button>
</form>
<Button
type="submit"
isLoading={isPending}
disabled={isPending}
>
{isPending ? 'Running Scan...' : 'Start Scan'}
</Button>
</form>
</>
);
};

View File

@ -65,7 +65,10 @@ export const ScansList = (): React.ReactElement => {
const scanDate = formatDate(scan.scan_date);
return (
<div key={scan.id} className="scans-list__row">
<div
key={scan.id}
className="scans-list__row"
>
<div className="scans-list__cell">
<span className="scans-list__url">{scan.target_url}</span>
</div>

View File

@ -59,10 +59,15 @@ export const TestResultCard = ({
{result.recommendations_json.length > 0 ? (
<div className="test-result-card__section">
<h4 className="test-result-card__section-title">Recommendations</h4>
<h4 className="test-result-card__section-title">
Recommendations
</h4>
<ul className="test-result-card__recommendations">
{result.recommendations_json.map((rec) => (
<li key={`${result.id.toString()}-rec-${rec}`} className="test-result-card__recommendation">
<li
key={`${result.id.toString()}-rec-${rec}`}
className="test-result-card__recommendation"
>
{rec}
</li>
))}
@ -78,9 +83,7 @@ export const TestResultCard = ({
className="test-result-card__evidence-toggle"
aria-expanded={showEvidence}
>
<span>
{showEvidence ? '▼' : '▶'} Technical Evidence
</span>
<span>{showEvidence ? '▼' : '▶'} Technical Evidence</span>
</button>
{showEvidence ? (
<pre className="test-result-card__evidence">

View File

@ -21,7 +21,8 @@ export const AUTH_ENDPOINTS = {
LOGIN: '/auth/login',
} as const;
export type AuthEndpoint = (typeof AUTH_ENDPOINTS)[keyof typeof AUTH_ENDPOINTS];
export type AuthEndpoint =
(typeof AUTH_ENDPOINTS)[keyof typeof AUTH_ENDPOINTS];
/**
* Auth Error Messages

View File

@ -3,10 +3,7 @@
// ©AngelaMos | 2025
// ===========================
import {
useMutation,
type UseMutationResult,
} from '@tanstack/react-query';
import { useMutation, type UseMutationResult } from '@tanstack/react-query';
import { isAxiosError, type AxiosError } from 'axios';
import { toast } from 'sonner';
import { useNavigate } from 'react-router-dom';
@ -29,12 +26,23 @@ import { useAuthStore } from '@/store/authStore';
const createAuthErrorHandler = (context: string) => {
return (error: unknown): void => {
if (isAxiosError(error) && error.response?.data !== null && error.response?.data !== undefined) {
if (
isAxiosError(error) &&
error.response?.data !== null &&
error.response?.data !== undefined
) {
const errorData: unknown = error.response.data;
if (typeof errorData === 'object' && errorData !== null && 'detail' in errorData) {
if (
typeof errorData === 'object' &&
errorData !== null &&
'detail' in errorData
) {
const apiError = errorData as { detail: unknown };
if (typeof apiError.detail === 'string' && apiError.detail.length > 0) {
if (
typeof apiError.detail === 'string' &&
apiError.detail.length > 0
) {
toast.error(apiError.detail);
return;
}

View File

@ -43,7 +43,9 @@ axiosInstance.interceptors.response.use(
(error: AxiosError) => {
if (error.response?.status === 401) {
const requestUrl = error.config?.url ?? '';
const isAuthEndpoint = requestUrl.includes('/auth/login') || requestUrl.includes('/auth/register');
const isAuthEndpoint =
requestUrl.includes('/auth/login') ||
requestUrl.includes('/auth/register');
if (!isAuthEndpoint) {
localStorage.removeItem(STORAGE_KEYS.AUTH_TOKEN);

View File

@ -8,7 +8,11 @@ import { toast } from 'sonner';
export const createApiErrorHandler = (context: string) => {
return (error: unknown): void => {
if (isAxiosError(error) && error.response?.data !== null && error.response?.data !== undefined) {
if (
isAxiosError(error) &&
error.response?.data !== null &&
error.response?.data !== undefined
) {
const errorData: unknown = error.response.data;
if (
@ -24,7 +28,9 @@ export const createApiErrorHandler = (context: string) => {
}
const fallbackMessage =
error instanceof Error ? error.message : `Operation failed: ${context}`;
error instanceof Error
? error.message
: `Operation failed: ${context}`;
toast.error(fallbackMessage);
};
};

View File

@ -33,7 +33,10 @@ export const DashboardPage = (): React.ReactElement => {
</div>
<div className="dashboard__header-actions">
<span className="dashboard__user-email">{user?.email}</span>
<Button onClick={handleLogout} variant="secondary">
<Button
onClick={handleLogout}
variant="secondary"
>
Logout
</Button>
</div>

View File

@ -27,7 +27,10 @@ export const ScanResultsPage = (): React.ReactElement => {
return (
<div className="scan-results__error">
<p>Failed to load scan results. Please try again.</p>
<Link to="/" className="scan-results__back-link">
<Link
to="/"
className="scan-results__back-link"
>
Back to Dashboard
</Link>
</div>
@ -38,7 +41,10 @@ export const ScanResultsPage = (): React.ReactElement => {
return (
<div className="scan-results__error">
<p>Scan not found.</p>
<Link to="/" className="scan-results__back-link">
<Link
to="/"
className="scan-results__back-link"
>
Back to Dashboard
</Link>
</div>
@ -55,7 +61,10 @@ export const ScanResultsPage = (): React.ReactElement => {
<div className="scan-results">
<div className="scan-results__container">
<header className="scan-results__header">
<Link to="/" className="scan-results__back-link">
<Link
to="/"
className="scan-results__back-link"
>
Back to Dashboard
</Link>
<h1 className="scan-results__title">Scan Results</h1>
@ -77,7 +86,9 @@ export const ScanResultsPage = (): React.ReactElement => {
</span>
</div>
<div className="scan-results__meta-item">
<span className="scan-results__meta-label">Vulnerabilities:</span>
<span className="scan-results__meta-label">
Vulnerabilities:
</span>
<span
className={`scan-results__vuln-count ${
vulnerableCount > 0
@ -93,7 +104,10 @@ export const ScanResultsPage = (): React.ReactElement => {
<div className="scan-results__tests">
{scan.test_results.map((result) => (
<TestResultCard key={result.id} result={result} />
<TestResultCard
key={result.id}
result={result}
/>
))}
</div>
</div>

View File

@ -37,6 +37,11 @@ export const router = createBrowserRouter([
},
{
path: '*',
element: <Navigate to="/" replace />,
element: (
<Navigate
to="/"
replace
/>
),
},
]);

View File

@ -41,9 +41,18 @@ interface UIState {
}
interface UIActions {
setLoginFormField: (field: keyof Omit<LoginFormState, 'expiresAt'>, value: string) => void;
setRegisterFormField: (field: keyof Omit<RegisterFormState, 'expiresAt'>, value: string) => void;
setScanFormField: (field: keyof Omit<ScanFormState, 'expiresAt'>, value: string | ScanTestType[]) => void;
setLoginFormField: (
field: keyof Omit<LoginFormState, 'expiresAt'>,
value: string,
) => void;
setRegisterFormField: (
field: keyof Omit<RegisterFormState, 'expiresAt'>,
value: string,
) => void;
setScanFormField: (
field: keyof Omit<ScanFormState, 'expiresAt'>,
value: string | ScanTestType[],
) => void;
toggleTestExpanded: (testId: number) => void;
clearLoginForm: () => void;
clearRegisterForm: () => void;
@ -113,7 +122,8 @@ export const useUIStore = create<UIStore>()(
toggleTestExpanded: (testId): void => {
set((state) => {
const currentValue = state.testResults.expandedTests[testId] ?? false;
const currentValue =
state.testResults.expandedTests[testId] ?? false;
state.testResults.expandedTests[testId] = !currentValue;
});
},

View File

@ -14,11 +14,7 @@ import type {
ScanStatus,
Severity,
} from './scan.types';
import {
SCAN_TEST_TYPES,
SCAN_STATUS,
SEVERITY,
} from '@/config/constants';
import { SCAN_TEST_TYPES, SCAN_STATUS, SEVERITY } from '@/config/constants';
export const isValidLoginResponse = (
data: unknown,
@ -89,7 +85,9 @@ const isValidTestResult = (data: unknown): data is TestResult => {
typeof obj.evidence_json === 'object' &&
obj.evidence_json !== null &&
Array.isArray(obj.recommendations_json) &&
obj.recommendations_json.every((rec: unknown) => typeof rec === 'string') &&
obj.recommendations_json.every(
(rec: unknown) => typeof rec === 'string',
) &&
typeof obj.created_at === 'string'
);
};