readysite / website / frontend / pages / Pages.jsx
10.0 KB
Pages.jsx
import * as React from 'react';
import { useState, useEffect, useCallback } from 'react';
import { Link } from 'react-router-dom';
import { apiGet, apiPost, apiDelete } from '../hooks/useAPI.js';
import { useChat } from '../layouts/AdminLayout.jsx';
import { TiltCard } from '../components/TiltCard.jsx';

export function Pages() {
  const { toggleChat } = useChat() || {};
  const [pages, setPages] = useState(null);
  const [loading, setLoading] = useState(true);
  const [creating, setCreating] = useState(false);
  const [showModal, setShowModal] = useState(false);

  const fetchPages = useCallback(() => {
    setLoading(true);
    apiGet('/api/admin/pages')
      .then(setPages)
      .finally(() => setLoading(false));
  }, []);

  useEffect(() => {
    fetchPages();
  }, [fetchPages]);

  const createPage = async (e) => {
    e.preventDefault();
    const form = e.target;
    const formData = new FormData(form);

    setCreating(true);
    try {
      await apiPost('/api/admin/pages', {
        title: formData.get('title'),
        description: formData.get('description'),
        parentId: formData.get('parent_id') || '',
      });
      setShowModal(false);
      form.reset();
      fetchPages();
    } finally {
      setCreating(false);
    }
  };

  const deletePage = async (id) => {
    if (!confirm('Delete this page?')) return;
    await apiDelete(`/api/admin/pages/${id}`);
    fetchPages();
  };

  return (
    <div className="flex flex-col h-full">
      {/* Header */}
      <div className="flex items-center justify-between px-4 py-3 border-b border-base-300 bg-base-200/50">
        <div>
          <h2 className="font-semibold">Pages</h2>
          <p className="text-xs text-base-content/50">Website content</p>
        </div>
        <div className="flex items-center gap-2">
          <Link to="/partials" className="btn btn-ghost btn-sm gap-1">
            <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z" />
            </svg>
            Partials
          </Link>
          <button onClick={() => setShowModal(true)} className="btn btn-ghost btn-sm gap-1">
            <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 4v16m8-8H4" />
            </svg>
            Page
          </button>
          {toggleChat && (
            <button onClick={toggleChat} className="btn btn-soft btn-primary btn-sm gap-2">
              <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
              </svg>
              AI Assistant
            </button>
          )}
        </div>
      </div>

      {/* Content */}
      <div className="flex-1 overflow-auto p-4">
        {loading ? (
          <div className="flex justify-center py-12">
            <span className="loading loading-spinner loading-lg" />
          </div>
        ) : pages?.items?.length ? (
          <div className="space-y-2 max-w-2xl mx-auto">
            {pages.items.map((page) => (
              <PageCard key={page.id} page={page} onDelete={deletePage} />
            ))}
          </div>
        ) : (
          <div className="flex flex-col items-center justify-center h-full text-center px-6">
            <div className="w-16 h-16 rounded-2xl bg-base-200 flex items-center justify-center mb-4">
              <svg xmlns="http://www.w3.org/2000/svg" className="h-8 w-8 text-base-content/30" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
              </svg>
            </div>
            <h3 className="text-lg font-semibold mb-1">No pages yet</h3>
            <p className="text-base-content/50 text-sm mb-4">Create your first page to get started.</p>
            <button onClick={() => setShowModal(true)} className="btn btn-primary btn-sm">Create First Page</button>
          </div>
        )}
      </div>

      {/* New Page Modal */}
      {showModal && (
        <dialog className="modal modal-open">
          <div className="modal-box w-11/12 max-w-2xl">
            <button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onClick={() => setShowModal(false)}>✕</button>
            <h3 className="font-bold text-lg mb-4">New Page</h3>

            <form onSubmit={createPage} className="space-y-4">
              <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
                <div className="form-control">
                  <label className="floating-label">
                    <span>Title</span>
                    <input type="text" name="title" placeholder="Page Title" className="input input-bordered w-full" required autoFocus />
                  </label>
                </div>

                <div className="form-control">
                  <label className="floating-label">
                    <span>Slug (optional)</span>
                    <input type="text" name="slug" placeholder="page-slug" className="input input-bordered w-full" pattern="[a-z0-9-]+" />
                  </label>
                  <label className="label">
                    <span className="label-text-alt text-base-content/50">Auto-generated from title if empty</span>
                  </label>
                </div>

                <div className="form-control md:col-span-2">
                  <label className="floating-label">
                    <span>Meta Description</span>
                    <input type="text" name="description" placeholder="Brief description for search engines" className="input input-bordered w-full" />
                  </label>
                </div>

                <div className="form-control">
                  <label className="label">
                    <span className="label-text text-xs">Parent Page</span>
                  </label>
                  <select name="parent_id" className="select select-bordered select-sm w-full">
                    <option value="">No Parent (Root)</option>
                    {pages?.items?.map((p) => (
                      <option key={p.id} value={p.id}>{p.title}</option>
                    ))}
                  </select>
                </div>

                <div className="form-control flex-row items-center gap-3 pt-5">
                  <input type="checkbox" name="published" className="checkbox checkbox-primary checkbox-sm" />
                  <span className="text-sm">Published</span>
                </div>
              </div>

              <div className="flex justify-end gap-2 pt-4">
                <button type="button" className="btn btn-ghost btn-sm" onClick={() => setShowModal(false)}>Cancel</button>
                <button type="submit" className="btn btn-primary btn-sm" disabled={creating}>
                  {creating ? <span className="loading loading-spinner loading-sm" /> : 'Create Page'}
                </button>
              </div>
            </form>
          </div>
          <div className="modal-backdrop" onClick={() => setShowModal(false)} />
        </dialog>
      )}
    </div>
  );
}

function PageCard({ page, onDelete }) {
  return (
    <TiltCard className={`block rounded-lg bg-base-100/80 backdrop-blur-sm border border-white/5 hover:shadow-lg hover:border-white/10 cursor-pointer border-l-4 ${page.published ? 'border-l-success' : 'border-l-warning'}`}>
      <Link to={`/pages/${page.id}`} className="block p-3">
        <div className="flex items-center justify-between">
          <div className="flex items-center gap-3">
            {page.published ? (
              <div className="w-8 h-8 rounded-full bg-success/15 flex items-center justify-center flex-shrink-0">
                <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-success" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
                </svg>
              </div>
            ) : (
              <div className="w-8 h-8 rounded-full bg-warning/15 flex items-center justify-center flex-shrink-0">
                <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-warning" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
                </svg>
              </div>
            )}
            <div>
              <span className="font-semibold text-base-content">{page.title}</span>
              <div className="text-xs text-base-content/40 font-mono">{page.path || '/'}</div>
            </div>
          </div>
          <div className="flex items-center gap-2">
            <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-base-content/30" fill="none" viewBox="0 0 24 24" stroke="currentColor">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5l7 7-7 7" />
            </svg>
          </div>
        </div>
        {page.description && (
          <p className="text-sm text-base-content/50 mt-1 ml-11 line-clamp-1">{page.description}</p>
        )}
      </Link>
    </TiltCard>
  );
}
← Back