Skip to main content

Overview

The Admin Panel provides authenticated administrators and editors with tools to manage users, publish content, and maintain the health information ecosystem. All admin features require specific permissions validated through the PermissionContext.

User Management

The user management interface is located at app/(main)/dashboard/admin/users/index.tsx.

Features

Administrators can create new users with specific roles:
app/(main)/dashboard/admin/users/index.tsx:177-205
const saveUser = async () => {
  if (!form.nombre || !form.email) {
    Alert.alert('Campos requeridos', 'Nombre, correo y contraseña son obligatorios');
    return;
  }

  if (!validatePasswords()) return;

  if (!editingUser) {
    // Require re-authentication for creating users
    setReauthAction('create');
    setReauthVisible(true);
    return;
  }

  const userData = {
    ...form,
    createdBy: currentUser?.name,
    password: form.password || undefined,
  };

  const res = await apiClient.post<User>('/users', userData);
  setUsers((prev) => [...prev, res.data]);
};

Re-Authentication System

Security Feature: Critical operations (create/delete users) require password re-authentication to prevent unauthorized actions.
app/(main)/dashboard/admin/users/index.tsx:227-235
const verifyCurrentUserPassword = async (password: string): Promise<boolean> => {
  try {
    const response = await apiClient.post<{ valid: boolean }>('/auth/verify', { password });
    return response.data.valid === true;
  } catch (error) {
    console.error('Error al verificar contraseña:', error);
    return false;
  }
};
The re-authentication modal appears when:
  1. Creating a new user
  2. Deleting an existing user

User Search & Filtering

Real-time search across multiple fields:
app/(main)/dashboard/admin/users/index.tsx:324-333
data={users.filter((user) => {
  const query = search.toLowerCase().trim();
  if (!query) return true;
  return (
    user.id.toString().includes(query) ||
    user.nombre.toLowerCase().includes(query) ||
    user.email.toLowerCase().includes(query) ||
    user.rol.toLowerCase().includes(query)
  );
})}

Role Assignment

Roles are loaded dynamically from the backend:
app/(main)/dashboard/admin/users/index.tsx:87-96
const loadRoles = async () => {
  try {
    const res = await apiClient.get<any[]>('/roles');
    setRoles(res.data);
  } catch (error) {
    console.log('Error cargando roles', error);
  }
};
Role selection UI with visual feedback:
app/(main)/dashboard/admin/users/index.tsx:437-452
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 8 }}>
  {roles.filter(r => r.activo === 1).map((role) => (
    <TouchableOpacity
      key={role.id}
      style={[
        styles.pickerOption,
        form.rol === role.nombre && styles.pickerSelected
      ]}
      onPress={() => setForm({ ...form, rol: role.nombre })}
    >
      <Text style={styles.pickerText}>{role.nombre}</Text>
      {form.rol === role.nombre && <Check color="#007AFF" size={16} />}
    </TouchableOpacity>
  ))}
</View>

Audit Trail

Each user record tracks creation and modification history:
app/(main)/dashboard/admin/users/index.tsx:547-593
<View style={styles.detailsSection}>
  {/* Created */}
  <View style={styles.detailRow}>
    <Text style={styles.label}>Creado:</Text>
    <Text style={styles.value}>
      {new Date(user.created_at).toLocaleString('es-PE', {
        day: '2-digit',
        month: '2-digit',
        year: 'numeric',
        hour: '2-digit',
        minute: '2-digit',
      })}
    </Text>
  </View>
  <View style={styles.detailRow}>
    <Text style={styles.label}>Por:</Text>
    <Text style={styles.value}>
      {typeof user.created_by === 'string' ? user.created_by : `ID: ${user.created_by}`}
    </Text>
  </View>

  {/* Updated */}
  {user.updated_at && (
    <>
      <View style={styles.detailRow}>
        <Text style={styles.label}>Editado:</Text>
        <Text style={styles.value}>
          {new Date(user.updated_at).toLocaleString('es-PE', {...})}
        </Text>
      </View>
    </>
  )}
</View>

Content Management

All content management routes follow a similar pattern and are located under app/(main)/dashboard/manage/.

Available Content Types

Noticias

Route: /manage/noticiasPublish institutional news with:
  • Title and description
  • Featured images
  • Facebook link integration
  • Publication date

Establecimientos

Route: /manage/establecimientosRegister health facilities with:
  • Name and address
  • Phone number
  • Google Maps link
  • Associated services

Servicios

Route: /manage/serviciosConfigure medical services:
  • Service name
  • Description
  • Availability

Estrategias

Route: /manage/estrategiasManage health programs:
  • Strategy title
  • Detailed description
  • Target population

Normas

Route: /manage/normasAdminister regulations:
  • Document title
  • Official links
  • Publication date

Roles

Route: /manage/rolesConfigure permissions:
  • Role name
  • Permission assignments
  • Active status

Permission Validation

All admin features validate permissions both on frontend and backend:

Frontend Permission Check

src/context/PermissionContext.tsx:50-53
const hasPermission = (code: string) => {
  if (user?.role === 'admin') return true;
  return permissions.includes(code);
}

Backend Validation

The backend API validates permissions on every protected endpoint using JWT tokens and role-based access control.
Best Practice: Frontend permission checks improve UX by hiding unavailable features, but server-side validation ensures security.

Role Color System

Visual role identification using deterministic colors:
app/(main)/dashboard/admin/users/index.tsx:30-52
const getRoleColor = (role: string) => {
  switch (role.toLowerCase()) {
    case 'admin': return '#007AFF'; // Azure
    case 'editor': return '#5856D6'; // Purple
    case 'user': return '#6c757d'; // Gray
    default:
      // Generate deterministic color from role name
      let hash = 0;
      for (let i = 0; i < role.length; i++) {
        hash = role.charCodeAt(i) + ((hash << 5) - hash);
      }
      const colors = [
        '#FF9500', '#FF3B30', '#34C759', '#00C7BE',
        '#AF52DE', '#FF2D55', '#A2845E', '#5AC8FA',
      ];
      return colors[Math.abs(hash) % colors.length];
  }
};

Password Management

Password Visibility Toggle

Secure password input with show/hide functionality:
app/(main)/dashboard/admin/users/index.tsx:380-398
<View style={styles.passwordInputContainer}>
  <TextInput
    style={styles.inputNoBorder}
    value={form.password}
    onChangeText={(text) => setForm({ ...form, password: text })}
    placeholder="Contraseña"
    secureTextEntry={!passwordVisible}
  />
  <TouchableOpacity
    style={styles.eyeButtonInInput}
    onPress={() => setPasswordVisible(!passwordVisible)}
  >
    {passwordVisible ? (
      <Eye color="#007AFF" size={18} />
    ) : (
      <EyeOff color="#6c757d" size={18} />
    )}
  </TouchableOpacity>
</View>

Password Confirmation

Real-time password match validation:
app/(main)/dashboard/admin/users/index.tsx:165-175
const validatePasswords = (): boolean => {
  if (!editingUser && !form.password) {
    Alert.alert('Error', 'La contraseña es obligatoria para nuevos usuarios');
    return false;
  }
  if (!editingUser && form.password !== form.confirmPassword) {
    Alert.alert('Error', 'Las contraseñas no coinciden');
    return false;
  }
  return true;
};

API Endpoints

User Management Endpoints

MethodEndpointPurposePermission
GET/usersList all usersmanage_users
GET/users/:idGet user detailsmanage_users
POST/usersCreate new usermanage_users
PUT/users/:idUpdate usermanage_users
DELETE/users/:idDelete usermanage_users
POST/auth/verifyVerify passwordAuthenticated

Content Management Endpoints

MethodEndpointPurpose
GET/rolesList available roles
GET/noticiasList news (admin)
POST/noticiasCreate news article
GET/establecimientosList establishments (admin)
POST/establecimientosRegister facility

Security Best Practices

Implemented Security Measures:
  1. Re-authentication for critical operations
  2. Permission validation on every protected route
  3. Audit trails with created_by/updated_by fields
  4. Self-deletion prevention for logged-in users
  5. JWT token validation on backend

Error Handling

Comprehensive error messages for common scenarios:
app/(main)/dashboard/admin/users/index.tsx:202-204
catch (error: any) {
  Alert.alert('Error', error.response?.data?.message || 'No se pudo guardar el usuario');
}

Dashboard

Role-based navigation system

Authentication

Login and session management

Build docs developers (and LLMs) love