uiXpress
Api

User Details Components API

Create custom components for the user details view using Vue.js or React components with the extensible component registration system

Table of Contents

Overview

The user details view uses a tabbed interface where components can be registered to specific categories (tabs). Components are self-contained Vue or React components that receive user data and handle their own logic. The system supports both Vue.js 3 and React components, allowing developers to choose their preferred framework.

Key Features

  • Tabbed Interface: Organize components into logical categories (Details, Activity, Commerce, etc.)
  • Self-Contained Components: Each component manages its own data and logic
  • Capability-Based Access: Control component visibility based on user permissions
  • Dynamic Props: Components receive user data and app store via props
  • Extensible API: Easy integration for external plugins
  • Framework Agnostic: Support for both Vue.js and React components

Component Structure

Each component consists of three main files. The component file extension depends on the framework you choose:

Vue Component Structure:

your-component/
├── metadata.js      # Component configuration
├── component.vue    # Vue component
└── index.js         # Export file

React Component Structure:

your-component/
├── metadata.js      # Component configuration
├── component.jsx    # React component
└── index.js         # Export file

File Structure

metadata.js

export default {
  id: 'unique-component-id',
  title: 'Component Title',
  category: 'details', // 'details', 'activity', 'commerce', or custom
  language: 'vue', // 'vue' or 'react'
  requires_capabilities: ['edit_users'], // Optional
};

component.vue (Vue Component)

<script setup>
import { ref, computed, onMounted, watch } from 'vue';

const props = defineProps({
  userId: {
    type: [String, Number],
    required: true,
  },
  userData: {
    type: Object,
    required: true,
  },
  appData: {
    type: Object,
    required: true,
  },
  id: {
    type: String,
    required: true,
  },
});

// Your component logic here
</script>

<template>
  <!-- Your component template -->
</template>

component.jsx (React Component)

import React, { useState, useEffect } from 'react';

/**
 * Your React Component
 * 
 * @param {Object} props - Component props
 * @param {string|number} props.userId - The user ID
 * @param {Object} props.userData - Complete user data object
 * @param {Object} props.appData - Vue app store instance
 * @param {string} props.id - Component ID from metadata
 */
const YourComponent = ({ userId, userData, appData, id }) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    // Load data when component mounts
    loadData();
  }, [userId]);

  const loadData = async () => {
    setLoading(true);
    try {
      // Your API call here
      // const response = await fetch(...);
      // setData(response);
    } catch (error) {
      console.error('Error loading data:', error);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="bg-zinc-50 dark:bg-zinc-800/20 border border-zinc-200/40 dark:border-zinc-800/60 rounded-xl p-6">
      {/* Your component content */}
    </div>
  );
};

export default YourComponent;

index.js

For Vue Components:

import metadata from './metadata.js';
import component from './component.vue';

export default {
  metadata,
  component,
};

For React Components:

import metadata from './metadata.js';
import component from './component.jsx';

export default {
  metadata,
  component,
};

Metadata Configuration

Required Properties

PropertyTypeDescriptionDefault
idstringUnique identifier for the componentRequired
titlestringDisplay title for the componentRequired
categorystringTab category (e.g., 'details', 'activity', 'commerce')Required
languagestringComponent language ('vue' or 'react')'vue'

Optional Properties

PropertyTypeDescription
requires_capabilitiesarrayWordPress capabilities required to view the component

Example Metadata

Vue Component Example:

export default {
  id: 'user-custom-fields',
  title: 'Custom Fields',
  category: 'details',
  language: 'vue',
  requires_capabilities: ['edit_users'],
};

React Component Example:

export default {
  id: 'user-social-links',
  title: 'Social Links',
  category: 'details',
  language: 'react', // Important: Set to 'react' for React components
  requires_capabilities: ['edit_users'],
};

Component Development

Props

Your component receives four props:

userId (String | Number)

The ID of the user being viewed.

Vue Example:

const userId = props.userId; // "123" or 123

React Example:

const userId = userId; // "123" or 123

userData (Object)

Complete user data object from WordPress REST API:

{
  id: number,
  name: string,
  email: string,
  slug: string,
  avatar_urls: object,
  registered_date: string,
  roles: array,
  capabilities: object,
  // ... other WordPress user fields
}

Vue Example:

const userName = props.userData.name;
const userEmail = props.userData.email;

React Example:

const userName = userData.name;
const userEmail = userData.email;

appData (Object)

The Vue app store instance containing:

{
  state: {
    adminUrl: string,           // WordPress admin URL
    siteURL: string,            // Site URL
    currentUser: object,        // Current user data
    uixpress_settings: object, // Plugin settings
    // ... other app state
  }
}

id (String)

The component ID from metadata, useful for unique identifiers or debugging.

Note for React Components: Veaury automatically converts Vue's kebab-case props (user-id, user-data, app-data) to React's camelCase (userId, userData, appData). However, it's recommended to handle both formats for maximum compatibility:

const userId = props.userId || props['user-id'];
const userData = props.userData || props['user-data'];
const appData = props.appData || props['app-data'];

Component Lifecycle

<script setup>
import { ref, computed, onMounted, watch } from 'vue';
import { lmnFetch } from '@/assets/js/functions/lmnFetch.js';

const props = defineProps({
  userId: { type: [String, Number], required: true },
  userData: { type: Object, required: true },
  appData: { type: Object, required: true },
});

// Component state
const data = ref([]);
const loading = ref(false);
const error = ref(null);

// Load data function
const loadData = async () => {
  loading.value = true;
  try {
    const args = {
      endpoint: `wp/v2/users/${props.userId}/custom-endpoint`,
    };
    const response = await lmnFetch(args);
    data.value = response.data;
  } catch (err) {
    error.value = err.message;
  } finally {
    loading.value = false;
  }
};

// Watch for user ID changes
watch(
  () => props.userId,
  () => {
    loadData();
  }
);

// Watch for user data changes
watch(
  () => props.userData,
  () => {
    // React to user data changes
  },
  { deep: true }
);

// Load data on mount
onMounted(() => {
  loadData();
});
</script>

Registration Methods

Internal Components (Plugin Development)

For components within the UIXpress plugin:

// In app/src/pages/users/src/components/index.js
import { addFilter } from '@/assets/js/functions/HooksSystem.js';
import myComponent from './my-component/index.js';

addFilter('uixpress/user-details/components/register', (components) => {
  return [...components, myComponent];
});

External Components (Other Plugins)

For external plugins, listen for the uixpress/user-details/ready event and use the global window.uixpress API:

// In your external plugin
document.addEventListener('uixpress/user-details/ready', () => {
  const { addFilter } = window.uixpress;

  addFilter('uixpress/user-details/components/register', (components) => {
    return [...components, myExternalComponent];
  });
});

Category Registration

Categories define the tabs available in the user details view. You can register custom categories:

Registering Categories

import { addFilter } from '@/assets/js/functions/HooksSystem.js';

addFilter('uixpress/user-details/categories/register', (categories) => {
  return [
    ...categories,
    { value: 'custom-tab', label: __('Custom Tab', 'uixpress') },
  ];
});

Default Categories

The following categories are registered by default:

  • details - User details and form fields
  • activity - User activity log (only shown if activity logger is enabled)
  • commerce - Commerce-related data (e.g., WooCommerce customer data)

Conditional Category Display

Categories can be conditionally displayed based on settings or plugin availability. For example, the activity category only appears when the activity logger is enabled, and the commerce category only appears when WooCommerce is active.

Capability Requirements

Control component visibility based on user permissions:

export default {
  id: 'admin-only-component',
  title: 'Admin Only',
  category: 'details',
  language: 'vue',
  requires_capabilities: ['manage_options'],
};

Multiple Capabilities

export default {
  id: 'editor-component',
  title: 'Editor Component',
  category: 'details',
  language: 'vue',
  requires_capabilities: ['edit_users', 'manage_options'],
};

If the user has any of the specified capabilities, the component will be visible.

Props and Attributes

The component-render component uses Vue's useAttrs() to pass through any additional props or attributes to your component. This means you can pass custom props when registering components:

// In your component registration
const myComponent = {
  metadata: {
    id: 'custom-component',
    title: 'Custom Component',
    category: 'details',
  },
  component: MyComponent,
};

// The component will receive all props passed to ComponentRender

Examples

Basic Vue Component

// metadata.js
export default {
  id: 'user-custom-info',
  title: 'Custom Information',
  category: 'details',
  language: 'vue',
};
<!-- component.vue -->
<script setup>
import { ref, onMounted } from 'vue';
import { lmnFetch } from '@/assets/js/functions/lmnFetch.js';

const props = defineProps({
  userId: { type: [String, Number], required: true },
  userData: { type: Object, required: true },
  appData: { type: Object, required: true },
});

const customData = ref(null);
const loading = ref(false);

const loadCustomData = async () => {
  loading.value = true;
  try {
    const args = {
      endpoint: `custom/v1/user/${props.userId}/info`,
    };
    const response = await lmnFetch(args);
    customData.value = response.data;
  } catch (error) {
    console.error('Failed to load custom data:', error);
  } finally {
    loading.value = false;
  }
};

onMounted(() => {
  loadCustomData();
});
</script>

<template>
  <div class="bg-white dark:bg-zinc-900 rounded-xl p-6 border border-zinc-200 dark:border-zinc-800">
    <h3 class="text-lg font-semibold mb-4">Custom Information</h3>
    
    <div v-if="loading" class="text-center py-4">
      <p class="text-sm text-zinc-500">Loading...</p>
    </div>
    
    <div v-else-if="customData" class="space-y-3">
      <div v-for="(value, key) in customData" :key="key" class="flex justify-between">
        <span class="text-sm text-zinc-500">{{ key }}:</span>
        <span class="text-sm font-medium">{{ value }}</span>
      </div>
    </div>
    
    <div v-else class="text-center py-4">
      <p class="text-sm text-zinc-500">No custom data available</p>
    </div>
  </div>
</template>

React Component Example

metadata.js:

export default {
  id: 'user-social-profile',
  title: 'Social Profile',
  category: 'details',
  language: 'react', // Important: Set to 'react'
};

component.jsx:

import React, { useState, useEffect } from 'react';

/**
 * User Social Profile Component
 * 
 * @param {Object} props - Component props
 * @param {string|number} props.userId - The user ID
 * @param {Object} props.userData - Complete user data object
 * @param {Object} props.appData - Vue app store instance
 */
const UserSocialProfile = (props) => {
  // Handle both camelCase and kebab-case prop names (veaury compatibility)
  const userId = props.userId || props['user-id'];
  const userData = props.userData || props['user-data'];
  const appData = props.appData || props['app-data'];

  const [socialLinks, setSocialLinks] = useState(null);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    loadSocialLinks();
  }, [userId]);

  const loadSocialLinks = async () => {
    setLoading(true);
    try {
      // Your API call here
      // const response = await fetch(...);
      // setSocialLinks(response);
    } catch (error) {
      console.error('Error loading social links:', error);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="bg-white dark:bg-zinc-900 rounded-xl p-6 border border-zinc-200 dark:border-zinc-800">
      <h3 className="text-lg font-semibold mb-4">Social Profile</h3>
      
      {loading ? (
        <div className="text-center py-4">
          <p className="text-sm text-zinc-500">Loading...</p>
        </div>
      ) : (
        <div className="space-y-3">
          {/* Your social links content */}
        </div>
      )}
    </div>
  );
};

export default UserSocialProfile;

External Plugin Integration

Vue Component Example:

// In your external plugin
(function () {
  'use strict';

  // Wait for UIXpress user details to be ready
  document.addEventListener('uixpress/user-details/ready', function (event) {
    const { addFilter } = window.uixpress;
    const { userId, userData } = event.detail;

    // Define your Vue component
    const myExternalComponent = {
      metadata: {
        id: 'my-plugin-component',
        title: 'My Plugin Data',
        category: 'details',
        language: 'vue',
        requires_capabilities: ['edit_users'],
      },
      component: {
        // Your Vue component definition
        template: `
          <div class="bg-white dark:bg-zinc-900 rounded-xl p-6">
            <h3>My Plugin Component</h3>
            <p>User ID: {{ userId }}</p>
            <p>User Name: {{ userData.name }}</p>
          </div>
        `,
        props: ['userId', 'userData', 'appData', 'id'],
      },
    };

    // Register the component
    addFilter('uixpress/user-details/components/register', function (components) {
      return [...components, myExternalComponent];
    });
  });
})();

React Component Example:

// In your external plugin
(function () {
  'use strict';

  // Wait for UIXpress user details to be ready
  document.addEventListener('uixpress/user-details/ready', function (event) {
    const { addFilter } = window.uixpress;
    const { userId, userData } = event.detail;

    // Define your React component
    const MyReactComponent = ({ userId, userData, appData, id }) => {
      return (
        <div className="bg-white dark:bg-zinc-900 rounded-xl p-6">
          <h3>My React Plugin Component</h3>
          <p>User ID: {userId}</p>
          <p>User Name: {userData.name}</p>
        </div>
      );
    };

    // Define your React component registration
    const myExternalReactComponent = {
      metadata: {
        id: 'my-react-plugin-component',
        title: 'My React Plugin Data',
        category: 'details',
        language: 'react', // Important: Set to 'react'
        requires_capabilities: ['edit_users'],
      },
      component: MyReactComponent,
    };

    // Register the component
    addFilter('uixpress/user-details/components/register', function (components) {
      return [...components, myExternalReactComponent];
    });
  });
})();

Best Practices

1. Component Design

  • Keep components focused on a single purpose
  • Use consistent spacing and typography
  • Follow the established design system (zinc color palette)
  • Ensure responsive behavior
  • Handle loading and error states gracefully

2. Performance

  • Implement proper loading states
  • Use efficient data fetching
  • Avoid unnecessary re-renders
  • Cache data when appropriate
  • Watch for prop changes efficiently

3. Error Handling

  • Always handle API errors gracefully
  • Provide meaningful error messages
  • Implement retry mechanisms when appropriate
  • Validate user data before using it

4. Accessibility

  • Use semantic HTML
  • Provide proper ARIA labels
  • Ensure keyboard navigation works
  • Test with screen readers

5. Security

  • Validate all user inputs
  • Use WordPress nonces for API calls
  • Respect capability requirements
  • Sanitize output data
  • Never trust client-side data

6. Testing

  • Test with different user roles
  • Verify capability requirements work correctly
  • Test responsive layouts
  • Validate API integrations
  • Test component lifecycle (mount, unmount, prop changes)

7. Component Organization

  • Group related components together
  • Use descriptive component IDs
  • Document complex logic
  • Keep components self-contained
  • Handle component-specific logic internally (don't rely on parent components)

Troubleshooting

Common Issues

  1. Component not appearing:
    • Check if user has required capabilities
    • Verify the category is registered
    • Check browser console for errors
    • Ensure component is properly registered
  2. Props not received:
    • Verify component metadata is correct
    • Check that props are defined in component
    • For React components, handle both camelCase and kebab-case
  3. API calls failing:
    • Verify nonce and permissions
    • Check endpoint URLs
    • Ensure proper error handling
  4. Styling issues:
    • Ensure Tailwind classes are available
    • Use zinc color palette for consistency
    • Check dark mode support

Debug Tips

// Add debugging to your component
console.log('User ID:', props.userId);
console.log('User Data:', props.userData);
console.log('App Data:', props.appData);
console.log('Component ID:', props.id);

Getting Help

  • Check the browser console for errors
  • Verify WordPress REST API endpoints
  • Test with different user roles
  • Review the UIXpress documentation
  • Check component registration timing

For more information, visit the UIXpress Documentation or contact support.