Client Portal System Implementation with Dynamic Collections and Enhanced UI

Summary

Implemented a comprehensive client portal system with dynamic collections for recommendations and projects, enhanced routing with client-specific URLs, and modern UI with CSS animations and responsive design.

Why Care

This client portal system provides a scalable foundation for delivering personalized content to different clients. The dynamic collection system allows for easy content management, while the enhanced UI with animations creates a professional, engaging user experience. The modular architecture makes it easy to add new clients and content types without code changes.

Implementation

Changes Made

Content Collections and Configuration

  • File: site/src/content.config.ts
    • Added clientRecommendationsCollection with glob pattern for **/Recommendations/**/*.{md,mdx}
    • Added clientProjectsCollection with glob pattern for **/Projects/**/*.{md,mdx}
    • Both collections include slug generation and title processing via processEntries
    • Updated collections export to include new client collections
    • Added path mappings for client content directories

Dynamic Routing System

  • File: site/src/pages/client/[client]/thread/[magazine].astro
    • Created dynamic routing for client-specific magazine pages
    • Supports both recommendations and projects collections
    • Automatically discovers clients from folder structure
    • Generates static paths for all client/magazine combinations
    • Implements proper sorting by date with fallback handling
  • File: site/src/pages/client/[client]/read/[...slug].astro
    • Created dedicated reader page for client content
    • Supports both individual essay viewing and collection browsing
    • Implements proper slug handling and navigation
    • Includes scroll-to-content functionality for direct essay links

Enhanced Client Portal Layout

  • File: site/src/layouts/ClientPortalLayout.astro
    • Removed reader section to reduce crowding
    • Added animated client portal cards section
    • Implemented staggered CSS animations for page load
    • Enhanced responsive design with mobile optimizations
    • Added proper icon styling with site accent colors
    • Maintained references and tooling sections

UI Components and Styling

  • File: site/src/content/messages/clientPortalCards.json
    • Created card configuration for Reader, Recommendations, and Projects
    • Added custom SVG icons with site accent color styling
    • Implemented proper routing paths for each card
  • File: site/src/components/basics/messages/IconHeaderMessageCardGrid.astro
    • Enhanced grid component with responsive design
    • Added proper card container styling
    • Implemented center alignment for odd-numbered card sets

CSS Animations and Styling

  • Added comprehensive animation system with keyframes:
    • fadeInSlideUp for section containers
    • fadeInContent for content wrappers
    • fadeInText, fadeInSubtitle, fadeInTitle, fadeInDescription for text elements
    • fadeInGrid for card grid animations
  • Implemented staggered timing (0.2s to 1.2s delays)
  • Used cubic-bezier easing for smooth, professional animations
  • Added responsive breakpoints for mobile optimization

Technical Details

Collection Schema Design

typescript
// Client collections follow consistent pattern
const clientRecommendationsCollection = defineCollection({
  loader: glob({
    pattern: "**/Recommendations/**/*.{md,mdx}",
    base: resolveContentPath("client-content")
  }),
  schema: z.object({
    aliases: z.union([
      z.string().transform(str => [str]),
      z.array(z.string()),
      z.null(),
      z.undefined()
    ]).transform(val => val ?? []).default([]),
  }).passthrough().transform((data, context) => {
    // Slug and title processing
    const filename = String(context.path).split('/').pop()?.replace(/\.(md|mdx)$/, '') || '';
    const displayTitle = data.title || filename.replace(/[-_]/g, ' ').replace(/\s+/g, ' ').trim();
    return {
      ...data,
      title: displayTitle,
      slug: filename.toLowerCase().replace(/\s+/g, '-'),
    };
  })
});

Dynamic Path Generation

typescript
// Automatic client discovery from folder structure
const clients = [...new Set(allEntries.map(entry => {
  const entryPath = String(entry.id);
  const pathParts = entryPath.split('/');
  return pathParts[0].toLowerCase();
}))];

// Generate paths for each client/magazine combination
for (const [magazineKey, { collection, urlPrefix }] of Object.entries(collectionMap)) {
  for (const client of clients) {
    const clientEntries = processedEntries.filter(entry => {
      const entryPath = String(entry.id);
      return entryPath.toLowerCase().includes(client.toLowerCase());
    });
    // Generate static paths...
  }
}

Animation System

css
/* Staggered animation implementation */
.client-portal-cards-section {
  animation: fadeInSlideUp 0.8s cubic-bezier(0.4, 0, 0.2, 1) forwards;
  opacity: 0;
  transform: translateY(30px);
}

.client-portal-cards-content {
  animation: fadeInContent 1s cubic-bezier(0.4, 0, 0.2, 1) 0.2s forwards;
  opacity: 0;
}

/* Keyframe definitions for smooth transitions */
@keyframes fadeInSlideUp {
  from {
    opacity: 0;
    transform: translateY(30px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

Integration Points

Content Structure Requirements

  • Client content must be organized in content/client-content/{ClientName}/ structure
  • Recommendations go in {ClientName}/Recommendations/ folder
  • Projects go in {ClientName}/Projects/ folder
  • Essays go in {ClientName}/essays/ folder
  • Each client needs proper cased names (e.g., "Laerdal" not "laerdal")

URL Structure

  • Client portal: /client/{client}
  • Reader: /client/{client}/read
  • Individual essays: /client/{client}/read/{essay-slug}
  • Recommendations: /client/{client}/thread/recommendations
  • Projects: /client/{client}/thread/projects

Dependencies

  • Requires processEntries utility for slug and title processing
  • Uses toProperCase utility for client name formatting
  • Integrates with existing IconHeaderMessageCardGrid component
  • Relies on CollectionReaderLayout for essay display

Documentation

Usage Examples

  1. Adding a new client: Create folder structure in content/client-content/{ClientName}/
  2. Adding recommendations: Place markdown files in {ClientName}/Recommendations/
  3. Adding projects: Place markdown files in {ClientName}/Projects/
  4. Customizing cards: Edit site/src/content/messages/clientPortalCards.json

File Structure

text
content/client-content/
├── Laerdal/
│   ├── Recommendations/
│   │   └── strategic-recommendations.md
│   ├── Projects/
│   │   └── active-projects.md
│   └── essays/
│       └── client-essays.md
└── OtherClient/
    └── ...

Performance Considerations

  • Static generation for all client routes ensures fast loading
  • CSS animations use GPU acceleration via transform properties
  • Responsive design prevents layout shifts on mobile devices
  • Lazy loading implemented for images and content

Future Enhancements

  • Support for additional content types (case studies, reports)
  • Client-specific theming and branding
  • Advanced filtering and search capabilities
  • Integration with external content management systems