Skip to content
44 changes: 22 additions & 22 deletions src/platform/docs/SELENIUM_TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,28 +175,28 @@ npx mocha --require ts-node/register --timeout 60000 tests/data/data.test.ts

### Coverage by Module

| Module | Pages | Tests | Status |
| ---------------- | -------------------- | ------- | ------------------ |
| **Auth** | Login | 8 | Covered |
| | Register | 7 | Covered |
| | Forgot Password | 4 | Covered |
| | Reset Password | 4 | Covered |
| | Protected Routes | 7 | Covered |
| | Form Validation | 5 | Covered |
| **User** | Home | 8 | Covered |
| | Profile | 9 | Covered |
| | Favorites/Analytics | 2 | Covered |
| | Request Organization | 4 | Covered |
| **Organization** | Dashboard | 3 | Covered |
| | Members | 4 | Covered |
| | Settings | 2 | Covered |
| | Roles | 2 | Covered |
| | Member Requests | 3 | Covered |
| **Admin** | All System Pages | 7 | Page loads covered |
| **Data** | Visualizer | 2 | Covered |
| | Export | 3 | Covered |
| | Map | 2 | Covered |
| **Total** | | **86** | |
| Module | Pages | Tests | Status |
| ---------------- | -------------------- | ------ | ------------------ |
| **Auth** | Login | 8 | Covered |
| | Register | 7 | Covered |
| | Forgot Password | 4 | Covered |
| | Reset Password | 4 | Covered |
| | Protected Routes | 7 | Covered |
| | Form Validation | 5 | Covered |
| **User** | Home | 8 | Covered |
| | Profile | 9 | Covered |
| | Favorites/Analytics | 2 | Covered |
| | Request Organization | 4 | Covered |
| **Organization** | Dashboard | 3 | Covered |
| | Members | 4 | Covered |
| | Settings | 2 | Covered |
| | Roles | 2 | Covered |
| | Member Requests | 3 | Covered |
| **Admin** | All System Pages | 7 | Page loads covered |
| **Data** | Visualizer | 2 | Covered |
| | Export | 3 | Covered |
| | Map | 2 | Covered |
| **Total** | | **86** | |

### What Each Test Suite Covers

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { toast } from '@/shared/components/ui';
import { registerSchema, type RegisterFormData } from '@/shared/lib/validators';
import {
FIRST_NAME_MAX,
LAST_NAME_MAX,
EMAIL_MAX,
PASSWORD_MAX,
} from '@/shared/lib/validation-limits';
import { useRegister } from '@/shared/hooks/useAuth';
import { getUserFriendlyErrorMessage } from '@/shared/utils/errorMessages';
import { useRouter } from 'next/navigation';
Expand Down Expand Up @@ -68,6 +74,7 @@ export default function RegisterPage() {
label="First name"
placeholder="Enter your first name"
error={errors.firstName?.message}
maxLength={FIRST_NAME_MAX}
{...register('firstName')}
/>
</div>
Expand All @@ -77,6 +84,7 @@ export default function RegisterPage() {
label="Last name"
placeholder="Enter your last name"
error={errors.lastName?.message}
maxLength={LAST_NAME_MAX}
{...register('lastName')}
/>
</div>
Expand All @@ -88,6 +96,7 @@ export default function RegisterPage() {
placeholder="Enter your email"
error={errors.email?.message}
containerClassName="mb-4"
maxLength={EMAIL_MAX}
{...register('email')}
/>

Expand All @@ -98,6 +107,7 @@ export default function RegisterPage() {
placeholder="Create password"
error={errors.password?.message}
showPasswordToggle
maxLength={PASSWORD_MAX}
{...register('password')}
/>{' '}
<p className="mt-2 text-xs text-gray-500">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
forgotPwdSchema,
type ForgotPwdFormData,
} from '@/shared/lib/validators';
import { EMAIL_MAX } from '@/shared/lib/validation-limits';
import { useForgotPassword } from '@/shared/hooks/useAuth';
import { getUserFriendlyErrorMessage } from '@/shared/utils/errorMessages';
import { useState } from 'react';
Expand Down Expand Up @@ -95,6 +96,7 @@ export default function ForgotPwdPage() {
placeholder="e.g. greta.nagawa@gmail.com"
error={errors.email?.message}
containerClassName="mb-4"
maxLength={EMAIL_MAX}
{...register('email')}
/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { toast } from '@/shared/components/ui';
import { resetPwdSchema, type ResetPwdFormData } from '@/shared/lib/validators';
import { PASSWORD_MAX } from '@/shared/lib/validation-limits';
import { useResetPassword } from '@/shared/hooks/useAuth';
import { useSearchParams } from 'next/navigation';
import { useEffect, useState } from 'react';
Expand Down Expand Up @@ -165,6 +166,7 @@ export default function ResetPwdPage() {
error={errors.password?.message}
containerClassName="mb-4"
showPasswordToggle
maxLength={PASSWORD_MAX}
{...register('password')}
/>

Expand All @@ -175,6 +177,7 @@ export default function ResetPwdPage() {
error={errors.confirmPassword?.message}
containerClassName="mb-4"
showPasswordToggle
maxLength={PASSWORD_MAX}
{...register('confirmPassword')}
/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { toast } from '@/shared/components/ui';
import { loginSchema, type LoginFormData } from '@/shared/lib/validators';
import { EMAIL_MAX, PASSWORD_MAX } from '@/shared/lib/validation-limits';
import {
normalizeCallbackUrl,
redirectWithReload,
Expand Down Expand Up @@ -184,6 +185,7 @@ export default function LoginPage() {
type="email"
placeholder="user@example.com"
error={errors.email?.message}
maxLength={EMAIL_MAX}
{...register('email')}
/>

Expand Down Expand Up @@ -220,6 +222,7 @@ export default function LoginPage() {
error={errors.password?.message}
containerClassName="mb-0"
showPasswordToggle
maxLength={PASSWORD_MAX}
{...register('password')}
/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ export default function HomePage() {
const [isModalOpen, setIsModalOpen] = useState(false);
const [isDataAccessOpen, setIsDataAccessOpen] = useState(false);

const fairUsagePolicyUrl = useEnvironmentAwareUrl('https://platform.airqo.net/docs/data-access/fair-usage-policy/');
const researchersGuideUrl = useEnvironmentAwareUrl('https://platform.airqo.net/docs/data-access/researchers-guide/');
const fairUsagePolicyUrl = useEnvironmentAwareUrl(
'https://platform.airqo.net/docs/data-access/fair-usage-policy/'
);
const researchersGuideUrl = useEnvironmentAwareUrl(
'https://platform.airqo.net/docs/data-access/researchers-guide/'
);

const handleModal = () => setIsModalOpen(!isModalOpen);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { toast } from '@/shared/components/ui';
import { loginSchema, type LoginFormData } from '@/shared/lib/validators';
import { EMAIL_MAX, PASSWORD_MAX } from '@/shared/lib/validation-limits';
import {
normalizeCallbackUrl,
redirectWithReload,
Expand Down Expand Up @@ -177,6 +178,7 @@ export default function OrgLoginPage() {
{...register('email')}
error={errors.email?.message}
placeholder="Enter your email"
maxLength={EMAIL_MAX}
/>

<Button type="submit" fullWidth disabled={loading}>
Expand Down Expand Up @@ -212,6 +214,7 @@ export default function OrgLoginPage() {
error={errors.password?.message}
placeholder="Enter your password"
showPasswordToggle
maxLength={PASSWORD_MAX}
/>

<div className="flex items-center justify-end gap-3">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
import { useRouter, useParams } from 'next/navigation';
import { PermissionGuard } from '@/shared/components';
import { useRBAC } from '@/shared/hooks';
import { EMAIL_MAX } from '@/shared/lib/validation-limits';

interface GroupMember {
_id: string;
Expand Down Expand Up @@ -464,6 +465,7 @@ const MembersPage: React.FC = () => {
}
placeholder="user@example.com"
className="w-full"
maxLength={EMAIL_MAX}
/>
{emailErrors[index] && (
<p className="mt-1 text-sm text-red-600 dark:text-red-400">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ import { getUserFriendlyErrorMessage } from '@/shared/utils/errorMessages';
import { formatWithPattern } from '@/shared/utils/dateUtils';
import { useUser } from '@/shared/hooks/useUser';
import { useRBAC } from '@/shared/hooks';
import {
ROLE_NAME_MAX,
ROLE_DESCRIPTION_MAX,
} from '@/shared/lib/validation-limits';

const RolesPage = () => {
const router = useRouter();
Expand Down Expand Up @@ -299,6 +303,7 @@ const RolesPage = () => {
placeholder="EXAMPLE_ROLE_NAME"
className="w-full"
disabled={createRoleMutation.isMutating}
maxLength={ROLE_NAME_MAX}
/>
</div>
<div>
Expand All @@ -312,6 +317,7 @@ const RolesPage = () => {
placeholder="Enter role description (optional)"
className="w-full"
disabled={createRoleMutation.isMutating}
maxLength={ROLE_DESCRIPTION_MAX}
/>
</div>
{currentOrg && (
Expand Down
14 changes: 14 additions & 0 deletions src/platform/src/app/(dashboard)/request-organization/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ import { toast } from '@/shared/components/ui/toast';
import Dialog from '@/shared/components/ui/dialog';
import { getUserFriendlyErrorMessage } from '@/shared/utils/errorMessages';
import { validateEmail } from '@/shared/lib/validators';
import {
PROJECT_NAME_MAX,
CITY_MAX,
FUNDER_PARTNER_MAX,
CONTACT_NAME_MAX,
EMAIL_MAX,
USE_CASE_MAX,
} from '@/shared/lib/validation-limits';

const ORGANIZATION_TYPES = [
{ value: 'government', label: 'Government' },
Expand Down Expand Up @@ -239,6 +247,7 @@ const RequestOrganizationPage = () => {
required
placeholder="Clean Air Monitor"
error={validationErrors.project_name}
maxLength={PROJECT_NAME_MAX}
/>

<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
Expand All @@ -250,6 +259,7 @@ const RequestOrganizationPage = () => {
required
placeholder="Kampala"
error={validationErrors.city}
maxLength={CITY_MAX}
/>

<SelectField
Expand Down Expand Up @@ -292,6 +302,7 @@ const RequestOrganizationPage = () => {
onChange={handleInputChange}
placeholder="WorldBank (optional)"
description="Optional: sponsor or implementation partner."
maxLength={FUNDER_PARTNER_MAX}
/>
</div>

Expand All @@ -304,6 +315,7 @@ const RequestOrganizationPage = () => {
rows={5}
placeholder="Describe what you are monitoring, who will use the data, and expected impact."
error={validationErrors.use_case}
maxLength={USE_CASE_MAX}
/>
</CardContent>
</Card>
Expand All @@ -325,6 +337,7 @@ const RequestOrganizationPage = () => {
required
placeholder="Jane Doe"
error={validationErrors.contact_name}
maxLength={CONTACT_NAME_MAX}
/>

<Input
Expand All @@ -336,6 +349,7 @@ const RequestOrganizationPage = () => {
required
placeholder="contact@example.com"
error={validationErrors.contact_email}
maxLength={EMAIL_MAX}
/>
</div>
</CardContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -899,7 +899,7 @@ const ClientDetailsPage: React.FC = () => {
<Button
variant="filled"
onClick={handleRefreshSecret}
disabled={isRefreshingSecret}
loading={isRefreshingSecret}
>
{isRefreshingSecret ? 'Regenerating...' : 'Regenerate Secret'}
</Button>
Expand Down
4 changes: 3 additions & 1 deletion src/platform/src/app/(dashboard)/system/clients/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ const ClientsAdminPage: React.FC = () => {
const {
data: clientsResponse,
isLoading,
isValidating,
error,
mutate,
} = useSWR('/api/clients', () => clientService.getClients());
Expand Down Expand Up @@ -437,6 +438,7 @@ const ClientsAdminPage: React.FC = () => {
variant="outlined"
Icon={AqRefreshCw05}
onClick={handleRefresh}
loading={isValidating}
className="ml-auto"
>
Refresh
Expand Down Expand Up @@ -559,7 +561,7 @@ const ClientsAdminPage: React.FC = () => {
<Button
variant="filled"
onClick={handleRefreshSecret}
disabled={isRefreshingSecret}
loading={isRefreshingSecret}
>
{isRefreshingSecret ? 'Regenerating...' : 'Regenerate Secret'}
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
getUserFriendlyErrorMessage,
isForbiddenError,
} from '@/shared/utils/errorMessages';
import { EMAIL_LIST_MAX } from '@/shared/lib/validation-limits';
import { AccessDenied } from '@/shared/components/AccessDenied';
import { sanitizeErrorForLogging } from '@/shared/utils/sanitizeErrorForLogging';

Expand Down Expand Up @@ -140,6 +141,7 @@ const EmailConfigContent: React.FC = () => {
data: response,
error,
isLoading,
isValidating,
mutate,
} = useSWR(
'/users/application-email-configs',
Expand Down Expand Up @@ -535,6 +537,7 @@ const EmailConfigContent: React.FC = () => {
Icon={AqRefreshCw05}
iconPosition="start"
onClick={handleRefresh}
loading={isValidating}
>
Refresh
</Button>
Expand Down Expand Up @@ -592,6 +595,7 @@ const EmailConfigContent: React.FC = () => {
onClick={handleRefresh}
Icon={AqRefreshCw05}
iconPosition="start"
loading={isValidating}
>
Try again
</Button>
Expand Down Expand Up @@ -663,6 +667,7 @@ const EmailConfigContent: React.FC = () => {
placeholder="admin1@airqo.net, admin2@airqo.net"
description="Comma-separated admin emails to copy on automated alerts."
required={dialogState?.mode === 'create'}
maxLength={EMAIL_LIST_MAX}
/>

{dialogState?.mode === 'edit' && (
Expand Down Expand Up @@ -705,6 +710,7 @@ const EmailConfigContent: React.FC = () => {
}
placeholder={'app-client@airqo.net\nmonitoring-bot@airqo.net'}
rows={6}
maxLength={EMAIL_LIST_MAX}
/>
<p className="mt-1.5 text-xs text-muted-foreground">
{dialogState?.mode === 'edit'
Expand Down
Loading
Loading