{
  "name": "Barber Log",
  "nodes": [
    {
      "parameters": {
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "filters": {
          "q": "Appointment Request from"
        }
      },
      "type": "n8n-nodes-base.gmailTrigger",
      "typeVersion": 1.3,
      "position": [
        0,
        0
      ],
      "id": "160d51b3-7fae-4cce-97b6-a5f8afa02819",
      "name": "Gmail Trigger",
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Barber Log: turns one booking notification email into one Google Sheet row.\n//\n// The notification sent by POST /booking carries the row as a JSON object\n// between two fixed sentinels in the email's plain-text part. This node pulls\n// out that block and runs a single JSON.parse on it. There is no label-hunting\n// left to break: previously this split the body on phrases like \"Phone #: \" and\n// \"Preferred Date 1:\", so any wording change silently shifted a field.\n//\n// Because of that, the email's HTML part is now purely presentational. It can be\n// redesigned freely -- new layout, colors, copy, whatever -- without touching\n// this node or the sheet, as long as the sentinel block stays in the plain-text\n// part and the subject keeps starting with \"Appointment Request from\".\n//\n// The twelve keys returned below map one-to-one, in this exact order, onto sheet\n// columns A..L, and empty values stay the string \"N/A\". server/src/Appointments.ts\n// reads that layout back and treats \"N/A\" as empty. Do not reorder, rename, add\n// or remove keys here without changing the sheet and Appointments.ts to match.\n\nconst START = \"---BOOKING_JSON_START---\";\nconst END = \"---BOOKING_JSON_END---\";\n\nconst body = $json.text || $json.textPlain || \"\";\n\nconst start = body.indexOf(START);\nif (start === -1) {\n  throw new Error(\"Booking email is missing \" + START);\n}\n\n// indexOf rather than split: splitting on the end sentinel returns the whole\n// remainder when it is absent, so a truncated email would parse whatever\n// happened to follow instead of failing the execution.\nconst end = body.indexOf(END, start);\nif (end === -1) {\n  throw new Error(\"Booking email is missing \" + END);\n}\n\nconst booking = JSON.parse(body.slice(start + START.length, end).trim());\n\n// The payload carries its own America/Los_Angeles timestamp. These fall back to\n// the email's received date, in the same timezone and format the sheet has\n// always used, so a message from any older sender still lands correctly.\nconst received = new Date($json.date);\nconst fallbackDate = received.toLocaleDateString(\"en-US\", {\n  timeZone: \"America/Los_Angeles\"\n});\nconst fallbackTime = received.toLocaleTimeString(\"en-US\", {\n  timeZone: \"America/Los_Angeles\",\n  hour: \"2-digit\",\n  minute: \"2-digit\"\n});\n\n// Empty becomes \"N/A\", matching what the sheet has always held.\nconst cell = (value) => {\n  const text = value === undefined || value === null ? \"\" : String(value).trim();\n  return text === \"\" ? \"N/A\" : text;\n};\n\n// Columns A..L, in order.\nreturn {\n  name: cell(booking.name),\n  date: cell(booking.date || fallbackDate),\n  time: cell(booking.time || fallbackTime),\n  phone: cell(booking.phone),\n  date1: cell(booking.date1),\n  avail1: cell(booking.avail1),\n  date2: cell(booking.date2),\n  avail2: cell(booking.avail2),\n  date3: cell(booking.date3),\n  avail3: cell(booking.avail3),\n  description: cell(booking.description),\n  email: cell(booking.email)\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        416,
        0
      ],
      "id": "f0cef7e7-bda7-4720-8bc2-47b8d8c0dfec",
      "name": "Code in JavaScript"
    },
    {
      "parameters": {
        "operation": "get",
        "messageId": "={{ $json.id }}",
        "simple": false,
        "options": {}
      },
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [
        208,
        0
      ],
      "id": "86ac8993-68e8-4809-bf05-f1cafc56ad53",
      "name": "Get a message",
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "18QTVoA7eT5Iga6bnys8X_Q32B2NBgP9epeV8Jul63hU",
          "mode": "list",
          "cachedResultName": "Barber Appointment Log Zapier (n8n)",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/18QTVoA7eT5Iga6bnys8X_Q32B2NBgP9epeV8Jul63hU/edit?usp=drivesdk"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Sheet1",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/18QTVoA7eT5Iga6bnys8X_Q32B2NBgP9epeV8Jul63hU/edit#gid=0"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Name": "={{ $json.name }}",
            "Date": "={{ $json.date }}",
            "Time": "={{ $json.time }}",
            "Phone #": "={{ $json.phone }}",
            "Preferred Date 1": "={{ $json.date1 }}",
            "Availability for Date 1": "={{ $json.avail1 }}",
            "Preferred Date 2": "={{ $json.date2 }}",
            "Availability for Date 2": "={{ $json.avail2 }}",
            "Preferred Date 3": "={{ $json.date3 }}",
            "Availability for Date 3": "={{ $json.avail3 }}",
            "Description of Haircut / Other Comments": "={{ $json.description }}",
            "Email": "={{ $json.email }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "Name",
              "displayName": "Name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Date",
              "displayName": "Date",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Time",
              "displayName": "Time",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Phone #",
              "displayName": "Phone #",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Preferred Date 1",
              "displayName": "Preferred Date 1",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Availability for Date 1",
              "displayName": "Availability for Date 1",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Preferred Date 2",
              "displayName": "Preferred Date 2",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Availability for Date 2",
              "displayName": "Availability for Date 2",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Preferred Date 3",
              "displayName": "Preferred Date 3",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Availability for Date 3",
              "displayName": "Availability for Date 3",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Description of Haircut / Other Comments",
              "displayName": "Description of Haircut / Other Comments",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Email",
              "displayName": "Email",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        624,
        0
      ],
      "id": "2be76b38-df27-4fa2-baa4-d04004fe491a",
      "name": "Append row in sheet",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Gmail Trigger": {
      "main": [
        [
          {
            "node": "Get a message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get a message": {
      "main": [
        [
          {
            "node": "Code in JavaScript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript": {
      "main": [
        [
          {
            "node": "Append row in sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate"
  },
  "staticData": {
    "node:Gmail Trigger": {
      "Gmail Trigger": {
        "lastTimeChecked": 1776657798
      }
    }
  },
  "triggerCount": 1,
  "meta": {
    "templateCredsSetupCompleted": true
  }
}