---
applyTo: 'src/app/[commerce]/[locale]/[currency]/account/**'
---

# Account Pages Development Instructions

## Overview

Account pages in Project Zero Next.js provide e-commerce account functionality using Next.js 14 App Router structure. These instructions apply to files in the `src/app/[commerce]/[locale]/[currency]/account/` directory.

## App Router Page Structure

### Basic Page Pattern

```tsx
// src/app/[commerce]/[locale]/[currency]/account/page.tsx
import { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'Account | Project Zero',
  description: 'User account page'
};

interface Props {
  params: {
    commerce: string;
    locale: string;
    currency: string;
  };
}

export default function AccountPage({ params }: Props) {
  return (
    <div>
      <h1>Account Page</h1>
      {/* Page content */}
    </div>
  );
}
```

### Layout Pattern

```tsx
// src/app/[commerce]/[locale]/[currency]/account/layout.tsx
interface Props {
  children: React.ReactNode;
  params: {
    commerce: string;
    locale: string;
    currency: string;
  };
}

export default function AccountLayout({ children, params }: Props) {
  return <div className="account-layout">{children}</div>;
}
```

## Component Import Patterns

### Theme Components

```tsx
import { Button, Input, Card } from '@theme/components';
```

### Akinon Next Components

```tsx
import { Image } from '@akinon/next/components/image';
import { Link } from '@akinon/next/components/link';
```

## Data Fetching

### Client-Side Hooks

Hooks used for fetching account data in Project Zero:

```tsx
'use client';

import {
  useGetProfileInfoQuery,
  useUpdateProfileMutation,
  useGetOrdersQuery,
  useGetOrderQuery,
  useGetOldOrdersQuery,
  useGetQuotationsQuery,
  useUpdateEmailMutation,
  useUpdatePasswordMutation,
  useSendContactMutation,
  useCancelOrderMutation,
  useBulkCancellationMutation,
  useGetCancellationReasonsQuery,
  useGetContactSubjectsQuery,
  usePasswordResetMutation,
  useGetBasketOffersQuery,
  useGetFutureBasketOffersQuery,
  useGetExpiredBasketOffersQuery,
  useGetDiscountItemsQuery,
  useAnonymizeMutation,
  useGetLoyaltyBalanceQuery,
  useGetLoyaltyTransactionsQuery
} from '@akinon/next/data/client/account';

// User profile information
const UserProfile = () => {
  const { data: profile, isLoading, error } = useGetProfileInfoQuery();

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error occurred</div>;

  return <div>Welcome, {profile?.first_name}</div>;
};

// Orders - with pagination support
const OrdersList = () => {
  const { data: orders, isLoading } = useGetOrdersQuery({
    page: 1,
    limit: 10
  });

  return (
    <div>
      <h2>Orders ({orders?.count})</h2>
      {orders?.results?.map((order) => (
        <div key={order.id}>
          <span>Order #{order.number}</span>
          <span>{order.status}</span>
          <span>{order.total} USD</span>
        </div>
      ))}
    </div>
  );
};

// Order detail
const OrderDetail = ({ orderId }) => {
  const { data: order, isLoading, error } = useGetOrderQuery(orderId);

  if (isLoading) return <div>Loading order...</div>;
  if (error) return <div>Order not found</div>;

  return (
    <div>
      <h1>Order #{order?.number}</h1>
      <p>Status: {order?.status}</p>
      <p>Total: {order?.total} USD</p>
      {/* Order products and details */}
    </div>
  );
};

// Old orders
const OldOrdersList = () => {
  const { data: oldOrders, isLoading } = useGetOldOrdersQuery({
    page: 1,
    limit: 10
  });

  if (isLoading) return <div>Loading...</div>;

  return (
    <div>
      <h2>Order History</h2>
      {oldOrders?.results?.map((order) => (
        <div key={order.id}>
          <span>#{order.number}</span>
          <span>{new Date(order.created_date).toLocaleDateString()}</span>
        </div>
      ))}
    </div>
  );
};

// B2B Quotations (if available)
const QuotationsList = () => {
  const { data: quotations, isLoading } = useGetQuotationsQuery({
    page: 1,
    limit: 10
  });

  if (isLoading) return <div>Loading quotations...</div>;

  return (
    <div>
      <h2>Active Quotations</h2>
      {quotations?.results?.map((quotation) => (
        <div key={quotation.id}>
          <h3>{quotation.title}</h3>
          <p>Status: {quotation.status}</p>
        </div>
      ))}
    </div>
  );
};

// Loyalty points (if available)
const LoyaltyInfo = () => {
  const { data: balance, isLoading: balanceLoading } =
    useGetLoyaltyBalanceQuery();
  const { data: transactions, isLoading: transactionsLoading } =
    useGetLoyaltyTransactionsQuery();

  if (balanceLoading || transactionsLoading) return <div>Loading...</div>;

  return (
    <div>
      <h2>Loyalty Points</h2>
      <p>Current Balance: {balance?.balance || 0} points</p>

      <h3>Recent Transactions</h3>
      <div>
        {transactions?.results?.map((transaction, index) => (
          <div key={index} className="border-b py-2">
            <span>{transaction.amount} points</span>
            <span className="text-gray-500 ml-2">
              {new Date(transaction.created_date).toLocaleDateString()}
            </span>
          </div>
        ))}
      </div>
    </div>
  );
};

// Basket offers
const BasketOffers = () => {
  const { data: currentOffers } = useGetBasketOffersQuery();
  const { data: futureOffers } = useGetFutureBasketOffersQuery();
  const { data: expiredOffers } = useGetExpiredBasketOffersQuery();

  return (
    <div>
      <h2>Basket Offers</h2>

      {currentOffers && (
        <div>
          <h3>Active Offers</h3>
          {/* Offer list */}
        </div>
      )}

      {futureOffers && (
        <div>
          <h3>Future Offers</h3>
          {/* Future offers */}
        </div>
      )}
    </div>
  );
};
```

### Mutation Hooks - Correct Usage Patterns

```tsx
'use client';

const ProfileForm = () => {
  const [updateProfile, { isLoading }] = useUpdateProfileMutation();
  const [updateEmail, { isLoading: emailLoading }] = useUpdateEmailMutation();
  const [updatePassword, { isLoading: passwordLoading }] =
    useUpdatePasswordMutation();

  const handleUpdateProfile = async (formData) => {
    try {
      const result = await updateProfile({
        first_name: formData.firstName,
        last_name: formData.lastName,
        phone: formData.phone,
        date_of_birth: formData.birthDate,
        gender: formData.gender,
        sms_allowed: formData.smsAllowed,
        email_allowed: formData.emailAllowed
      }).unwrap();

      // Success handling
      console.log('Profile updated:', result);
    } catch (error) {
      // Error handling
      console.error('Profile update error:', error);
    }
  };

  const handleChangeEmail = async (emailData) => {
    try {
      await updateEmail({
        new_email: emailData.newEmail,
        current_password: emailData.currentPassword
      }).unwrap();
    } catch (error) {
      console.error('Email change error:', error);
    }
  };

  const handleChangePassword = async (passwordData) => {
    try {
      await updatePassword({
        old_password: passwordData.oldPassword,
        new_password1: passwordData.newPassword,
        new_password2: passwordData.confirmPassword
      }).unwrap();
    } catch (error) {
      console.error('Password change error:', error);
    }
  };

  return (
    <div className="space-y-6">
      {/* Profile form */}
      <button
        onClick={handleUpdateProfile}
        disabled={isLoading}
        className="bg-blue-500 text-white p-2 rounded disabled:opacity-50"
      >
        {isLoading ? 'Saving...' : 'Update Profile'}
      </button>
    </div>
  );
};

// Order cancellation
const OrderActions = ({ orderId }) => {
  const [cancelOrder, { isLoading }] = useCancelOrderMutation();
  const { data: cancellationReasons } = useGetCancellationReasonsQuery();

  const handleCancelOrder = async (reasonId) => {
    try {
      await cancelOrder({
        id: orderId,
        reason: reasonId
      }).unwrap();

      // Show success message
    } catch (error) {
      console.error('Order cancellation error:', error);
    }
  };

  return (
    <div>
      <h3>Cancel Order</h3>
      {cancellationReasons?.map((reason) => (
        <button
          key={reason.id}
          onClick={() => handleCancelOrder(reason.id)}
          disabled={isLoading}
          className="block w-full text-left p-2 hover:bg-gray-100 disabled:opacity-50"
        >
          {reason.text}
        </button>
      ))}
    </div>
  );
};

// Contact form
const ContactForm = () => {
  const [sendContact, { isLoading }] = useSendContactMutation();
  const { data: subjects } = useGetContactSubjectsQuery();

  const handleSubmit = async (formData) => {
    try {
      const form = new FormData();
      form.append('subject', formData.subject);
      form.append('message', formData.message);
      form.append('order_number', formData.orderNumber || '');

      if (formData.attachment) {
        form.append('attachment', formData.attachment);
      }

      await sendContact(form).unwrap();

      // Success message
    } catch (error) {
      console.error('Contact form submission error:', error);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <select name="subject" required>
        <option value="">Select subject</option>
        {subjects?.map((subject) => (
          <option key={subject.id} value={subject.id}>
            {subject.text}
          </option>
        ))}
      </select>

      <textarea name="message" required placeholder="Your message" />

      <input type="file" name="attachment" />

      <button
        type="submit"
        disabled={isLoading}
        className="bg-blue-500 text-white p-2 rounded disabled:opacity-50"
      >
        {isLoading ? 'Sending...' : 'Send'}
      </button>
    </form>
  );
};

// Account anonymization (GDPR)
const AccountSettings = () => {
  const [anonymize, { isLoading }] = useAnonymizeMutation();

  const handleAnonymize = async () => {
    if (confirm('Are you sure you want to permanently delete your account?')) {
      try {
        const result = await anonymize().unwrap();

        // Redirect user to logout page
        window.location.href = '/logout';
      } catch (error) {
        console.error('Account anonymization error:', error);
      }
    }
  };

  return (
    <div className="bg-red-50 p-4 rounded border border-red-200">
      <h3 className="text-red-800 font-semibold">Delete Account</h3>
      <p className="text-red-700 text-sm mb-4">
        This action cannot be undone. All your data will be permanently deleted.
      </p>
      <button
        onClick={handleAnonymize}
        disabled={isLoading}
        className="bg-red-500 text-white px-4 py-2 rounded disabled:opacity-50"
      >
        {isLoading ? 'Deleting...' : 'Delete Account'}
      </button>
    </div>
  );
};
```

## State Management

### Redux Store

```tsx
'use client';

import { useAppSelector, useAppDispatch } from '@akinon/next/redux/store';

const AccountComponent = () => {
  const dispatch = useAppDispatch();
  const { user, isLoading } = useAppSelector((state) => state.account);

  // Redux actions
  const handleUpdate = () => {
    dispatch(/* account action */);
  };

  return <div>{/* Component */}</div>;
};
```

## Form Handling

### React Hook Form

```tsx
'use client';

import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';
import { useLocalization } from '@akinon/next/hooks/useLocalization';

const profileSchema = yup.object({
  first_name: yup.string().required('First name is required'),
  last_name: yup.string().required('Last name is required'),
  phone: yup.string().required('Phone is required'),
  date_of_birth: yup.date().nullable(),
  gender: yup.string(),
  sms_allowed: yup.boolean(),
  email_allowed: yup.boolean()
});

const ProfileForm = () => {
  const { t } = useLocalization();
  const [updateProfile, { isLoading }] = useUpdateProfileMutation();

  const {
    register,
    handleSubmit,
    formState: { errors }
  } = useForm({
    resolver: yupResolver(profileSchema)
  });

  const onSubmit = async (data) => {
    try {
      await updateProfile(data).unwrap();
      // Success message
    } catch (error) {
      // Error handling
    }
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        {...register('first_name')}
        placeholder="First Name"
        className="border p-2 rounded"
      />
      {errors.first_name && (
        <span className="text-red-500">{errors.first_name.message}</span>
      )}

      <input
        {...register('last_name')}
        placeholder="Last Name"
        className="border p-2 rounded"
      />
      {errors.last_name && (
        <span className="text-red-500">{errors.last_name.message}</span>
      )}

      <button
        type="submit"
        disabled={isLoading}
        className="bg-blue-500 text-white p-2 rounded"
      >
        {isLoading ? 'Saving...' : 'Save'}
      </button>
    </form>
  );
};
```

## Styling

### TailwindCSS

```tsx
const AccountPage = () => {
  return (
    <div className="container mx-auto px-4 py-8">
      <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
        <div className="bg-white rounded-lg shadow p-6">
          <h2 className="text-xl font-semibold mb-4">Profile</h2>
          {/* Content */}
        </div>
      </div>
    </div>
  );
};
```

### Conditional Classes

```tsx
import { clsx } from 'clsx';

const StatusBadge = ({ status }) => {
  return (
    <span
      className={clsx('px-2 py-1 rounded text-sm', {
        'bg-green-100 text-green-800': status === 'completed',
        'bg-yellow-100 text-yellow-800': status === 'pending',
        'bg-red-100 text-red-800': status === 'cancelled'
      })}
    >
      {status}
    </span>
  );
};
```

## Localization

### Usage

```tsx
import { useLocalization } from '@akinon/next/hooks/useLocalization';

const AccountComponent = () => {
  const { t } = useLocalization();

  return (
    <div>
      <h1>{t('account.title')}</h1>
      <p>{t('account.welcome_message', { name: 'User' })}</p>
    </div>
  );
};
```

## Error Handling

### API Errors

```tsx
const AccountComponent = () => {
  const { data, error, isLoading } = useGetProfileInfoQuery();

  if (error) {
    return (
      <div className="bg-red-50 border border-red-200 rounded p-4">
        <p className="text-red-700">
          {error.data?.message || 'An error occurred'}
        </p>
      </div>
    );
  }

  if (isLoading) {
    return <div className="animate-pulse">Loading...</div>;
  }

  return <div>{/* Normal content */}</div>;
};
```

## Performance

### Loading States

```tsx
const LoadingSkeleton = () => (
  <div className="animate-pulse space-y-4">
    <div className="h-4 bg-gray-200 rounded w-3/4"></div>
    <div className="h-4 bg-gray-200 rounded w-1/2"></div>
    <div className="h-32 bg-gray-200 rounded"></div>
  </div>
);
```

### Lazy Loading

```tsx
import { LazyComponent } from '@akinon/next/components/lazy-component';

const LazyOrderHistory = LazyComponent(() => import('./OrderHistory'));
```

## Best Practices

### TypeScript

```tsx
interface User {
  id: number;
  first_name: string;
  last_name: string;
  email: string;
}

interface Order {
  id: number;
  number: string;
  status: string;
  total: number;
}

interface Address {
  id: number;
  title: string;
  address: string;
  city: string;
  district: string;
}
```

### File Organization

```
account/
├── page.tsx              # Main account page
├── layout.tsx            # Layout (optional)
├── profile/
│   └── page.tsx         # Profile page
├── orders/
│   ├── page.tsx         # Orders list
│   └── [id]/
│       └── page.tsx     # Order detail
└── addresses/
    ├── page.tsx         # Address list
    └── new/
        └── page.tsx     # New address
```

---

**Note:** These instructions are based on Project Zero's existing API and hook structures. Follow existing patterns when adding new features.

## Important Notes

### Hook Usage Rules

1. **Query hooks** run automatically, no manual triggering required
2. **Mutation hooks** return array destructuring with `[mutationFn, { isLoading, error }]`
3. Use **unwrap()** for promise-based error handling
4. **isLoading** state should be used in UI for loading states

### Error Handling Best Practices

```tsx
// RTK Query error handling
const { data, error, isLoading } = useGetOrdersQuery();

if (error) {
  // RTK Query error structure
  const errorMessage =
    error.data?.message || error.error || 'An error occurred';
  return <div className="text-red-500">{errorMessage}</div>;
}
```

### Conditional Rendering

```tsx
// Show alternative content when no data
const OrdersList = () => {
  const { data: orders, isLoading } = useGetOrdersQuery();

  if (isLoading) return <LoadingSkeleton />;

  if (!orders?.results?.length) {
    return (
      <div className="text-center py-8">
        <p>You don't have any orders yet.</p>
        <Link href="/products" className="text-blue-500">
          Start shopping
        </Link>
      </div>
    );
  }

  return (
    <div>
      {orders.results.map((order) => (
        <OrderCard key={order.id} order={order} />
      ))}
    </div>
  );
};
```
