# 🔗 LinkWeasel + Sanity.io Integration Guide

Complete step-by-step guide to connect LinkWeasel with your Sanity.io CMS.

---

## 📋 Prerequisites

- Active Sanity.io account (free tier works!)
- Sanity project created
- LinkWeasel account configured

---

## 🚀 Quick Start (5 Minutes)

### Step 1: Get Your Sanity Project ID

1. Go to [sanity.io](https://www.sanity.io/)
2. Log in to your account
3. Select your project
4. Click **⚙️ Settings** in the left sidebar
5. Copy your **Project ID**
   - Format: `abc123xyz`
   - Example: `4p2r5t8x`

### Step 2: Generate API Token

1. In your Sanity project, go to **⚙️ Settings**
2. Click **API** in the left menu
3. Scroll to **Tokens** section
4. Click **Add API token**
5. Configure:
   - **Name:** `LinkWeasel`
   - **Permissions:** `Editor` (can read and write)
6. Click **Add token**
7. **⚠️ COPY THE TOKEN IMMEDIATELY** (you won't see it again!)
   - Format: `sk...` (starts with 'sk')

### Step 3: Connect LinkWeasel

#### Option A: Manual Publishing (Dashboard)

1. Go to LinkWeasel Dashboard: `/dashboard.html`
2. Generate an article
3. Click **"📤 Publish to CMS"**
4. Select **Sanity.io** from dropdown
5. Enter your credentials:
   - **Project ID:** `your-project-id`
   - **Dataset:** `production` (or your dataset name)
   - **API Token:** `sk...` (paste your token)
   - **Document Type:** `post` (or your schema type)
6. Choose **Save as Draft** or **Publish Immediately**
7. Click **🚀 Publish Article**

#### Option B: Automation (Set & Forget)

1. Go to Automation Settings: `/automation.html`
2. Toggle **Automation ON**
3. Configure:
   - **Website URL:** Your site
   - **Frequency:** Daily/Weekly
   - **CMS Platform:** Select **Sanity.io**
4. Enter Sanity credentials (same as above)
5. Click **💾 Save Automation Settings**
6. Done! Articles will publish automatically!

---

## 🎯 Sanity Schema Requirements

LinkWeasel publishes documents with this structure. Make sure your Sanity schema supports these fields:

### Minimal Schema (Required)

```javascript
// schemas/post.js
export default {
  name: 'post',
  title: 'Blog Post',
  type: 'document',
  fields: [
    {
      name: 'title',
      title: 'Title',
      type: 'string',
      validation: Rule => Rule.required()
    },
    {
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      options: {
        source: 'title',
        maxLength: 96,
      },
      validation: Rule => Rule.required()
    },
    {
      name: 'body',
      title: 'Body',
      type: 'array',
      of: [{ type: 'block' }]
    },
    {
      name: 'excerpt',
      title: 'Excerpt',
      type: 'text',
      rows: 4
    },
    {
      name: 'publishedAt',
      title: 'Published at',
      type: 'datetime'
    }
  ]
}
```

### Recommended Schema (SEO Optimized)

```javascript
// schemas/post.js
export default {
  name: 'post',
  title: 'Blog Post',
  type: 'document',
  fields: [
    {
      name: 'title',
      title: 'Title',
      type: 'string',
      validation: Rule => Rule.required()
    },
    {
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      options: {
        source: 'title',
        maxLength: 96,
      },
      validation: Rule => Rule.required()
    },
    {
      name: 'body',
      title: 'Body',
      type: 'array',
      of: [
        { type: 'block' },
        {
          type: 'image',
          options: { hotspot: true }
        }
      ]
    },
    {
      name: 'excerpt',
      title: 'Excerpt',
      type: 'text',
      rows: 4
    },
    {
      name: 'mainImage',
      title: 'Main image',
      type: 'image',
      options: {
        hotspot: true
      }
    },
    {
      name: 'categories',
      title: 'Categories',
      type: 'array',
      of: [{ type: 'string' }]
    },
    {
      name: 'tags',
      title: 'Tags',
      type: 'array',
      of: [{ type: 'string' }]
    },
    {
      name: 'seo',
      title: 'SEO',
      type: 'object',
      fields: [
        {
          name: 'metaDescription',
          title: 'Meta Description',
          type: 'text',
          rows: 3
        },
        {
          name: 'metaKeywords',
          title: 'Meta Keywords',
          type: 'array',
          of: [{ type: 'string' }]
        }
      ]
    },
    {
      name: 'publishedAt',
      title: 'Published at',
      type: 'datetime'
    },
    {
      name: 'author',
      title: 'Author',
      type: 'reference',
      to: [{ type: 'author' }]
    }
  ],
  preview: {
    select: {
      title: 'title',
      author: 'author.name',
      media: 'mainImage'
    }
  }
}
```

### Adding the Schema to Your Sanity Project

1. Open your Sanity Studio project locally
2. Navigate to `schemas/` folder
3. Create or edit `post.js` with the schema above
4. Import it in `schemas/schema.js`:

```javascript
// schemas/schema.js
import post from './post'

export default createSchema({
  name: 'default',
  types: schemaTypes.concat([
    post,
    // ... other schemas
  ]),
})
```

5. Run `sanity deploy` to update your studio

---

## 🔐 Security Best Practices

### API Token Permissions

**✅ DO:**
- Use **Editor** permission (can read and write)
- Create a dedicated token for LinkWeasel
- Keep token secret (never commit to git)

**❌ DON'T:**
- Use **Admin** permission (unnecessary)
- Share tokens between applications
- Expose tokens in client-side code

### Token Storage

**For Manual Publishing:**
- Enter token each time (most secure)
- Or use browser's password manager

**For Automation:**
- Store in environment variables
- Use Vercel's encrypted environment storage
- Never store in localStorage for production

---

## 🎨 Custom Document Types

Using a different document type instead of `post`?

### Example: "article" Document Type

1. In LinkWeasel, set **Document Type** to: `article`
2. Make sure your Sanity schema has a document named `article`
3. LinkWeasel will publish to that type instead!

### Example: E-commerce Product Descriptions

```javascript
// Can even use for products!
{
  name: 'product',
  title: 'Product',
  type: 'document',
  fields: [
    { name: 'title', type: 'string' },
    { name: 'description', type: 'array', of: [{ type: 'block' }] },
    { name: 'longDescription', type: 'array', of: [{ type: 'block' }] },
    // ... other fields
  ]
}
```

Set Document Type to: `product`

---

## 📚 Datasets

### What is a Dataset?

Datasets are separate content environments in Sanity:
- `production` - Live content
- `staging` - Test content
- `development` - Local development

### Using Different Datasets

**Production (Default):**
```
Dataset: production
```

**Staging/Testing:**
```
Dataset: staging
```

LinkWeasel will publish to whichever dataset you specify!

---

## 🔧 Troubleshooting

### Error: "Unauthorized"

**Problem:** Invalid API token

**Solutions:**
1. Verify token is copied correctly (starts with `sk`)
2. Check token has **Editor** permissions
3. Generate a new token if needed

### Error: "Unknown document type"

**Problem:** Document type doesn't exist in schema

**Solutions:**
1. Check your schema has a document named `post` (or your custom type)
2. Run `sanity deploy` after schema changes
3. Verify Document Type field matches schema name exactly

### Error: "Project not found"

**Problem:** Invalid Project ID

**Solutions:**
1. Double-check Project ID from Sanity settings
2. Make sure you're using the correct project
3. Project ID is case-sensitive

### Articles Appear as Draft in Sanity

**This is normal if:**
- You selected "Save as Draft" in LinkWeasel
- Check `publishedAt` field - if null, it's a draft

**To auto-publish:**
- Select "Publish Immediately" in LinkWeasel
- Articles will have `publishedAt` timestamp set

---

## 🎯 Advanced: Using Sanity with Next.js

If you're using Sanity with a Next.js frontend:

### 1. Create a Blog Index Page

```javascript
// pages/blog/index.js
import { client } from '../../lib/sanity'

export default function Blog({ posts }) {
  return (
    <div>
      {posts.map(post => (
        <article key={post._id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </div>
  )
}

export async function getStaticProps() {
  const posts = await client.fetch(`
    *[_type == "post" && defined(publishedAt)] | order(publishedAt desc)
  `)

  return { props: { posts }, revalidate: 60 }
}
```

### 2. Render LinkWeasel Content

```javascript
// components/PortableText.js
import { PortableText } from '@portabletext/react'

export default function Article({ content }) {
  return <PortableText value={content} />
}
```

---

## 💡 Tips & Tricks

### 1. Content Preview Before Publishing

Set to **"Save as Draft"** to review articles in Sanity Studio before publishing to your live site.

### 2. Bulk Content Generation

Use automation mode to generate multiple articles:
- Set frequency to "Daily"
- Articles auto-save as drafts
- Review and publish batch at once

### 3. SEO Optimization

LinkWeasel automatically includes:
- Meta descriptions
- Target keywords
- Proper heading structure
- Long-form content (3000+ words)

### 4. Custom Fields

Need additional fields? Contact support to customize what fields LinkWeasel populates!

---

## 📞 Need Help?

- **Sanity Docs:** https://www.sanity.io/docs
- **LinkWeasel Issues:** https://github.com/WhiteCoatMDorg/seo_agent/issues
- **Community Support:** Check our Discord

---

## ✅ Checklist

Before publishing your first article, make sure you have:

- [ ] Sanity project created
- [ ] Project ID copied
- [ ] API token generated (Editor permissions)
- [ ] Post schema configured in Sanity
- [ ] Schema deployed (`sanity deploy`)
- [ ] Credentials entered in LinkWeasel
- [ ] Test article published successfully

---

## 🎉 You're All Set!

LinkWeasel is now connected to your Sanity.io CMS and ready to generate SEO-optimized content automatically!

**Next Steps:**
1. Generate your first article manually to test
2. Set up automation for hands-free content generation
3. Watch your SEO rankings improve! 📈
