mirror of
https://github.com/taogaetz/chefbible.git
synced 2025-12-06 11:47:24 -05:00
added intelligent ingredient parsing and updated form look. changed native form submission to spa style json api
This commit is contained in:
parent
2a577cf6ab
commit
f5c4a5e008
226
package-lock.json
generated
226
package-lock.json
generated
@ -9,7 +9,8 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/client": "^6.14.0",
|
"@prisma/client": "^6.14.0",
|
||||||
"@prisma/extension-accelerate": "^2.0.2"
|
"@prisma/extension-accelerate": "^2.0.2",
|
||||||
|
"convert": "^5.12.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/compat": "^1.2.5",
|
"@eslint/compat": "^1.2.5",
|
||||||
@ -815,7 +816,6 @@
|
|||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
|
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nodelib/fs.stat": "2.0.5",
|
"@nodelib/fs.stat": "2.0.5",
|
||||||
@ -829,7 +829,6 @@
|
|||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
|
||||||
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
|
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
@ -839,7 +838,6 @@
|
|||||||
"version": "1.2.8",
|
"version": "1.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
|
||||||
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
|
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nodelib/fs.scandir": "2.1.5",
|
"@nodelib/fs.scandir": "2.1.5",
|
||||||
@ -1956,6 +1954,31 @@
|
|||||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/anymatch": {
|
||||||
|
"version": "3.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||||
|
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"normalize-path": "^3.0.0",
|
||||||
|
"picomatch": "^2.0.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/anymatch/node_modules/picomatch": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/argparse": {
|
"node_modules/argparse": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||||
@ -1990,6 +2013,18 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/binary-extensions": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "1.1.12",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
@ -2005,7 +2040,6 @@
|
|||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fill-range": "^7.1.1"
|
"fill-range": "^7.1.1"
|
||||||
@ -2160,6 +2194,15 @@
|
|||||||
"node": "^14.18.0 || >=16.10.0"
|
"node": "^14.18.0 || >=16.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/convert": {
|
||||||
|
"version": "5.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/convert/-/convert-5.12.0.tgz",
|
||||||
|
"integrity": "sha512-gwi1eN0dZsVI//Z+VHPgosbV0u3vxCgPT5p9H3pK5RyGrHrKaQSUNKxoGTYFbkJWv+qYaGY4w394gDNNaXer/g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"wireit": "0.14.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cookie": {
|
"node_modules/cookie": {
|
||||||
"version": "0.6.0",
|
"version": "0.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
||||||
@ -2650,7 +2693,6 @@
|
|||||||
"version": "3.3.3",
|
"version": "3.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
||||||
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
|
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nodelib/fs.stat": "^2.0.2",
|
"@nodelib/fs.stat": "^2.0.2",
|
||||||
@ -2667,7 +2709,6 @@
|
|||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-glob": "^4.0.1"
|
"is-glob": "^4.0.1"
|
||||||
@ -2694,7 +2735,6 @@
|
|||||||
"version": "1.19.1",
|
"version": "1.19.1",
|
||||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
|
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
|
||||||
"integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
|
"integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"reusify": "^1.0.4"
|
"reusify": "^1.0.4"
|
||||||
@ -2732,7 +2772,6 @@
|
|||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"to-regex-range": "^5.0.1"
|
"to-regex-range": "^5.0.1"
|
||||||
@ -2783,7 +2822,6 @@
|
|||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
@ -2855,7 +2893,6 @@
|
|||||||
"version": "4.2.11",
|
"version": "4.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/graphemer": {
|
"node_modules/graphemer": {
|
||||||
@ -2912,11 +2949,22 @@
|
|||||||
"node": ">=0.8.19"
|
"node": ">=0.8.19"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-binary-path": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"binary-extensions": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-extglob": {
|
"node_modules/is-extglob": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@ -2926,7 +2974,6 @@
|
|||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-extglob": "^2.1.1"
|
"is-extglob": "^2.1.1"
|
||||||
@ -2939,7 +2986,6 @@
|
|||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.12.0"
|
"node": ">=0.12.0"
|
||||||
@ -3006,6 +3052,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/jsonc-parser": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/keyv": {
|
"node_modules/keyv": {
|
||||||
"version": "4.5.4",
|
"version": "4.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||||
@ -3340,7 +3392,6 @@
|
|||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||||
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
|
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
@ -3350,7 +3401,6 @@
|
|||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"braces": "^3.0.3",
|
"braces": "^3.0.3",
|
||||||
@ -3364,7 +3414,6 @@
|
|||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.6"
|
"node": ">=8.6"
|
||||||
@ -3485,6 +3534,15 @@
|
|||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/normalize-path": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/nypm": {
|
"node_modules/nypm": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.1.tgz",
|
||||||
@ -3928,6 +3986,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/proper-lockfile": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"graceful-fs": "^4.2.4",
|
||||||
|
"retry": "^0.12.0",
|
||||||
|
"signal-exit": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
@ -3959,7 +4028,6 @@
|
|||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||||
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@ -4021,11 +4089,19 @@
|
|||||||
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/retry": {
|
||||||
|
"version": "0.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
|
||||||
|
"integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/reusify": {
|
"node_modules/reusify": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
|
||||||
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
|
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"iojs": ">=1.0.0",
|
"iojs": ">=1.0.0",
|
||||||
@ -4076,7 +4152,6 @@
|
|||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||||
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
|
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@ -4152,6 +4227,12 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/signal-exit": {
|
||||||
|
"version": "3.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||||
|
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/sirv": {
|
"node_modules/sirv": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz",
|
||||||
@ -4345,7 +4426,6 @@
|
|||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-number": "^7.0.0"
|
"is-number": "^7.0.0"
|
||||||
@ -4585,6 +4665,110 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/wireit": {
|
||||||
|
"version": "0.14.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/wireit/-/wireit-0.14.12.tgz",
|
||||||
|
"integrity": "sha512-gNSd+nZmMo6cuICezYXRIayu6TSOeCSCDzjSF0q6g8FKDsRbdqrONrSZYzdk/uBISmRcv4vZtsno6GyGvdXwGA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"workspaces": [
|
||||||
|
"vscode-extension",
|
||||||
|
"website"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": "^4.0.0",
|
||||||
|
"chokidar": "^3.5.3",
|
||||||
|
"fast-glob": "^3.2.11",
|
||||||
|
"jsonc-parser": "^3.0.0",
|
||||||
|
"proper-lockfile": "^4.1.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"wireit": "bin/wireit.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/wireit/node_modules/balanced-match": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-vjtV3hiLqYDNRoiAv0zC4QaGAMPomEoq83PRmYIofPswwZurCeWR5LByXm7SyoL0Zh5+2z0+HC7jG8gSZJUh0w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/wireit/node_modules/brace-expansion": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-YClrbvTCXGe70pU2JiEiPLYXO9gQkyxYeKpJIQHVS/gOs6EWMQP2RYBwjFLNT322Ji8TOC3IMPfsYCedNpzKfA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"balanced-match": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/wireit/node_modules/chokidar": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"anymatch": "~3.1.2",
|
||||||
|
"braces": "~3.0.2",
|
||||||
|
"glob-parent": "~5.1.2",
|
||||||
|
"is-binary-path": "~2.1.0",
|
||||||
|
"is-glob": "~4.0.1",
|
||||||
|
"normalize-path": "~3.0.0",
|
||||||
|
"readdirp": "~3.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8.10.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "~2.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/wireit/node_modules/glob-parent": {
|
||||||
|
"version": "5.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||||
|
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"is-glob": "^4.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/wireit/node_modules/picomatch": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/wireit/node_modules/readdirp": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"picomatch": "^2.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/word-wrap": {
|
"node_modules/word-wrap": {
|
||||||
"version": "1.2.5",
|
"version": "1.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||||
|
|||||||
@ -43,6 +43,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/client": "^6.14.0",
|
"@prisma/client": "^6.14.0",
|
||||||
"@prisma/extension-accelerate": "^2.0.2"
|
"@prisma/extension-accelerate": "^2.0.2",
|
||||||
|
"convert": "^5.12.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageProps } from './$types';
|
import type { PageProps } from './$types';
|
||||||
import { page } from '$app/stores';
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
let { data }: PageProps = $props();
|
let { data }: PageProps = $props();
|
||||||
const { recipe } = data;
|
const { recipe } = data;
|
||||||
|
let scale = $state(1);
|
||||||
|
|
||||||
|
function formatQuantity(q: number | null | undefined): string {
|
||||||
|
if (q == null) return '';
|
||||||
|
const scaled = q * scale;
|
||||||
|
return Number(scaled.toFixed(2)).toString();
|
||||||
|
}
|
||||||
|
|
||||||
function goBack() {
|
function goBack() {
|
||||||
goto('/');
|
goto('/');
|
||||||
@ -20,7 +26,7 @@
|
|||||||
<header class="border-b border-base-300 bg-base-100/90 p-5 backdrop-blur-md">
|
<header class="border-b border-base-300 bg-base-100/90 p-5 backdrop-blur-md">
|
||||||
<button
|
<button
|
||||||
class="btn gap-2 text-base-content/70 btn-ghost hover:bg-base-200 hover:text-base-content"
|
class="btn gap-2 text-base-content/70 btn-ghost hover:bg-base-200 hover:text-base-content"
|
||||||
on:click={goBack}
|
onclick={goBack}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
width="20"
|
width="20"
|
||||||
@ -104,6 +110,18 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<span class="font-semibold">{recipe.station}</span>
|
<span class="font-semibold">{recipe.station}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<label class="font-semibold text-base-content/70">Scale</label>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0.25"
|
||||||
|
max="4"
|
||||||
|
step="0.25"
|
||||||
|
bind:value={scale}
|
||||||
|
class="range range-primary"
|
||||||
|
/>
|
||||||
|
<span class="badge">{scale}×</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -122,7 +140,7 @@
|
|||||||
</span>
|
</span>
|
||||||
{#if recipeIngredient.quantity || recipeIngredient.unit}
|
{#if recipeIngredient.quantity || recipeIngredient.unit}
|
||||||
<span class="font-medium text-base-content/70">
|
<span class="font-medium text-base-content/70">
|
||||||
{recipeIngredient.quantity}
|
{formatQuantity(recipeIngredient.quantity)}
|
||||||
{recipeIngredient.unit}
|
{recipeIngredient.unit}
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@ -1,60 +1,65 @@
|
|||||||
import type { Actions } from './$types';
|
// import type { Actions } from './$types';
|
||||||
import { fail, redirect } from '@sveltejs/kit';
|
// import { fail } from '@sveltejs/kit';
|
||||||
import prisma from '$lib/server/prisma';
|
// import prisma from '$lib/server/prisma';
|
||||||
|
|
||||||
export const actions: Actions = {
|
// export const actions: Actions = {
|
||||||
create: async ({ request }) => {
|
// create: async ({ request }) => {
|
||||||
const formData = await request.formData();
|
// const formData = await request.formData();
|
||||||
|
|
||||||
const name = (formData.get('name') as string | null)?.trim() ?? '';
|
// const name = (formData.get('name') as string | null)?.trim() ?? '';
|
||||||
const description = (formData.get('description') as string | null)?.trim() || null;
|
// const description = (formData.get('description') as string | null)?.trim() || null;
|
||||||
const instructions = (formData.get('instructions') as string | null)?.trim() || null;
|
// const instructions = (formData.get('instructions') as string | null)?.trim() || null;
|
||||||
const time = ((formData.get('time') as string | null)?.trim() || 'Medium') as string;
|
// const time = ((formData.get('time') as string | null)?.trim() || 'Medium') as string;
|
||||||
const station = ((formData.get('station') as string | null)?.trim() || 'Pans') as string;
|
// const station = ((formData.get('station') as string | null)?.trim() || 'Pans') as string;
|
||||||
const ingredientsRaw = (formData.get('ingredients') as string | null) || '';
|
// const parsedIngredientsRaw = formData.get('parsedIngredients') as string | null;
|
||||||
|
// let parsedIngredients: Array<{ name: string; quantity: number | null; unit: string | null; prep: string | null }> = [];
|
||||||
|
|
||||||
if (!name) {
|
// if (!name) {
|
||||||
return fail(400, { message: 'Name is required', name, description, instructions, time, station, ingredients: ingredientsRaw });
|
// return fail(400, { message: 'Name is required', name, description, instructions, time, station });
|
||||||
}
|
// }
|
||||||
|
|
||||||
const recipe = await prisma.recipe.create({
|
// // Parse the ingredients JSON from client
|
||||||
data: {
|
// if (parsedIngredientsRaw) {
|
||||||
name,
|
// try {
|
||||||
description,
|
// parsedIngredients = JSON.parse(parsedIngredientsRaw);
|
||||||
instructions,
|
// } catch (error) {
|
||||||
time,
|
// console.error('Failed to parse ingredients JSON:', error);
|
||||||
station
|
// return fail(400, { message: 'Invalid ingredients format', name, description, instructions, time, station });
|
||||||
}
|
// }
|
||||||
});
|
// }
|
||||||
|
|
||||||
const ingredients = Array.from(
|
// const recipe = await prisma.recipe.create({
|
||||||
new Set(
|
// data: {
|
||||||
ingredientsRaw
|
// name,
|
||||||
.split(/\r?\n/)
|
// description,
|
||||||
.map((l) => l.trim())
|
// instructions,
|
||||||
.filter((l) => l.length > 0)
|
// time,
|
||||||
)
|
// station
|
||||||
);
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
if (ingredients.length > 0) {
|
// if (parsedIngredients.length > 0) {
|
||||||
for (const ingredientName of ingredients) {
|
// for (const parsed of parsedIngredients) {
|
||||||
const ingredient = await prisma.ingredient.upsert({
|
// if (!parsed.name) continue;
|
||||||
where: { name: ingredientName },
|
// const ingredient = await prisma.ingredient.upsert({
|
||||||
update: {},
|
// where: { name: parsed.name },
|
||||||
create: { name: ingredientName }
|
// update: {},
|
||||||
});
|
// create: { name: parsed.name }
|
||||||
|
// });
|
||||||
|
// await prisma.recipeIngredient.create({
|
||||||
|
// data: {
|
||||||
|
// recipeId: recipe.id,
|
||||||
|
// ingredientId: ingredient.id,
|
||||||
|
// quantity: parsed.quantity,
|
||||||
|
// unit: parsed.unit,
|
||||||
|
// prep: parsed.prep
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
await prisma.recipeIngredient.create({
|
// return { location: `/recipe/${recipe.id}` };
|
||||||
data: {
|
// }
|
||||||
recipeId: recipe.id,
|
// };
|
||||||
ingredientId: ingredient.id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw redirect(303, `/recipe/${recipe.id}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,45 +1,339 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
let pending = $state(false);
|
let pending = $state(false);
|
||||||
let lastError = $state<string | null>(null);
|
let lastError = $state<string | null>(null);
|
||||||
|
let lastSuccess = $state<string | null>(null);
|
||||||
|
let ingredientsText = $state('');
|
||||||
|
let formSubmitted = $state(false);
|
||||||
|
|
||||||
|
// Parsing functions for live preview
|
||||||
|
function parseMixedNumber(input: string): number | null {
|
||||||
|
const trimmed = input.trim();
|
||||||
|
if (!trimmed) return null;
|
||||||
|
// e.g. 1 1/2, 3/4, 2.5
|
||||||
|
const parts = trimmed.split(/\s+/);
|
||||||
|
let total = 0;
|
||||||
|
for (const part of parts) {
|
||||||
|
if (/^\d+\/\d+$/.test(part)) {
|
||||||
|
const [num, den] = part.split('/').map(Number);
|
||||||
|
if (den === 0) return null;
|
||||||
|
total += num / den;
|
||||||
|
} else if (/^\d+(?:\.\d+)?$/.test(part)) {
|
||||||
|
total += Number(part);
|
||||||
|
} else {
|
||||||
|
// unexpected token
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unitAliases: Record<string, { canonical: string; system: 'volume' | 'mass' | 'count' }> = {
|
||||||
|
// volume
|
||||||
|
tsp: { canonical: 'ml', system: 'volume' },
|
||||||
|
'tsp.': { canonical: 'ml', system: 'volume' },
|
||||||
|
teaspoon: { canonical: 'ml', system: 'volume' },
|
||||||
|
teaspoons: { canonical: 'ml', system: 'volume' },
|
||||||
|
tbsp: { canonical: 'ml', system: 'volume' },
|
||||||
|
'tbsp.': { canonical: 'ml', system: 'volume' },
|
||||||
|
tablespoon: { canonical: 'ml', system: 'volume' },
|
||||||
|
tablespoons: { canonical: 'ml', system: 'volume' },
|
||||||
|
cup: { canonical: 'ml', system: 'volume' },
|
||||||
|
cups: { canonical: 'ml', system: 'volume' },
|
||||||
|
ml: { canonical: 'ml', system: 'volume' },
|
||||||
|
milliliter: { canonical: 'ml', system: 'volume' },
|
||||||
|
milliliters: { canonical: 'ml', system: 'volume' },
|
||||||
|
l: { canonical: 'ml', system: 'volume' },
|
||||||
|
liter: { canonical: 'ml', system: 'volume' },
|
||||||
|
liters: { canonical: 'ml', system: 'volume' },
|
||||||
|
pt: { canonical: 'ml', system: 'volume' },
|
||||||
|
qt: { canonical: 'ml', system: 'volume' },
|
||||||
|
gal: { canonical: 'ml', system: 'volume' },
|
||||||
|
floz: { canonical: 'ml', system: 'volume' },
|
||||||
|
'fl-oz': { canonical: 'ml', system: 'volume' },
|
||||||
|
'fl oz': { canonical: 'ml', system: 'volume' },
|
||||||
|
// mass
|
||||||
|
g: { canonical: 'g', system: 'mass' },
|
||||||
|
gram: { canonical: 'g', system: 'mass' },
|
||||||
|
grams: { canonical: 'g', system: 'mass' },
|
||||||
|
kg: { canonical: 'g', system: 'mass' },
|
||||||
|
kilogram: { canonical: 'g', system: 'mass' },
|
||||||
|
kilograms: { canonical: 'g', system: 'mass' },
|
||||||
|
oz: { canonical: 'g', system: 'mass' },
|
||||||
|
ounce: { canonical: 'g', system: 'mass' },
|
||||||
|
ounces: { canonical: 'g', system: 'mass' },
|
||||||
|
lb: { canonical: 'g', system: 'mass' },
|
||||||
|
lbs: { canonical: 'g', system: 'mass' },
|
||||||
|
pound: { canonical: 'g', system: 'mass' },
|
||||||
|
pounds: { canonical: 'g', system: 'mass' },
|
||||||
|
// count / generic pieces stay as-is
|
||||||
|
clove: { canonical: 'clove', system: 'count' },
|
||||||
|
cloves: { canonical: 'clove', system: 'count' },
|
||||||
|
piece: { canonical: 'pc', system: 'count' },
|
||||||
|
pieces: { canonical: 'pc', system: 'count' },
|
||||||
|
pc: { canonical: 'pc', system: 'count' }
|
||||||
|
};
|
||||||
|
|
||||||
|
function parseIngredientLine(
|
||||||
|
line: string
|
||||||
|
): { name: string; quantity: number | null; unit: string | null; prep: string | null } | null {
|
||||||
|
const raw = line.trim();
|
||||||
|
if (!raw) return null;
|
||||||
|
|
||||||
|
// Split the line into parts
|
||||||
|
const parts = raw.split(/\s+/);
|
||||||
|
|
||||||
|
// If only one part, it's just a name
|
||||||
|
if (parts.length === 1) {
|
||||||
|
return { name: parts[0], quantity: null, unit: null, prep: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if first part is a quantity
|
||||||
|
const firstPart = parts[0];
|
||||||
|
const quantity = parseMixedNumber(firstPart);
|
||||||
|
|
||||||
|
if (quantity !== null) {
|
||||||
|
// We have a quantity, check if second part is a unit
|
||||||
|
if (parts.length >= 3) {
|
||||||
|
const secondPart = parts[1];
|
||||||
|
// Check if second part looks like a unit (short, alphanumeric, and in our known units)
|
||||||
|
if (secondPart.length <= 4 && /^[a-zA-Z]+$/.test(secondPart)) {
|
||||||
|
const unitLower = secondPart.toLowerCase();
|
||||||
|
// Only treat as unit if it's in our known unit aliases
|
||||||
|
if (unitAliases[unitLower]) {
|
||||||
|
const unit = unitLower;
|
||||||
|
const name = parts.slice(2).join(' ');
|
||||||
|
// Check if there's prep info after a comma
|
||||||
|
const commaIndex = name.indexOf(',');
|
||||||
|
let finalName = name;
|
||||||
|
let prep = null;
|
||||||
|
if (commaIndex !== -1) {
|
||||||
|
finalName = name.substring(0, commaIndex);
|
||||||
|
prep = name.substring(commaIndex + 1).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: finalName.trim(),
|
||||||
|
quantity,
|
||||||
|
unit,
|
||||||
|
prep
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have a quantity but no clear unit, check if it's a no-space unit like "3g"
|
||||||
|
if (firstPart.length > 1) {
|
||||||
|
const match = firstPart.match(/^(\d+(?:[\s-]+\d+\/\d+|\/\d+|\.\d+)?)([a-zA-Z]+)$/);
|
||||||
|
if (match) {
|
||||||
|
const [, qtyStr, unit] = match;
|
||||||
|
const qtyNum = parseMixedNumber(qtyStr);
|
||||||
|
if (qtyNum !== null) {
|
||||||
|
const unitLower = unit.toLowerCase();
|
||||||
|
// Only treat as unit if it's in our known unit aliases
|
||||||
|
if (unitAliases[unitLower]) {
|
||||||
|
const name = parts.slice(1).join(' ');
|
||||||
|
const commaIndex = name.indexOf(',');
|
||||||
|
let finalName = name;
|
||||||
|
let prep = null;
|
||||||
|
if (commaIndex !== -1) {
|
||||||
|
finalName = name.substring(0, commaIndex);
|
||||||
|
prep = name.substring(commaIndex + 1).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: finalName.trim(),
|
||||||
|
quantity: qtyNum,
|
||||||
|
unit: unitLower,
|
||||||
|
prep
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quantity but no unit found
|
||||||
|
const name = parts.slice(1).join(' ');
|
||||||
|
const commaIndex = name.indexOf(',');
|
||||||
|
let finalName = name;
|
||||||
|
let prep = null;
|
||||||
|
if (commaIndex !== -1) {
|
||||||
|
finalName = name.substring(0, commaIndex);
|
||||||
|
prep = name.substring(commaIndex + 1).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: finalName.trim(),
|
||||||
|
quantity,
|
||||||
|
unit: null,
|
||||||
|
prep
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// No quantity found in first part, check if it's a no-space unit like "1g"
|
||||||
|
if (firstPart.length > 1) {
|
||||||
|
const match = firstPart.match(/^(\d+(?:[\s-]+\d+\/\d+|\/\d+|\.\d+)?)([a-zA-Z]+)$/);
|
||||||
|
if (match) {
|
||||||
|
const [, qtyStr, unit] = match;
|
||||||
|
const qtyNum = parseMixedNumber(qtyStr);
|
||||||
|
if (qtyNum !== null) {
|
||||||
|
const unitLower = unit.toLowerCase();
|
||||||
|
// Only treat as unit if it's in our known unit aliases
|
||||||
|
if (unitAliases[unitLower]) {
|
||||||
|
const name = parts.slice(1).join(' ');
|
||||||
|
const commaIndex = name.indexOf(',');
|
||||||
|
let finalName = name;
|
||||||
|
let prep = null;
|
||||||
|
if (commaIndex !== -1) {
|
||||||
|
finalName = name.substring(0, commaIndex);
|
||||||
|
prep = name.substring(commaIndex + 1).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: finalName.trim(),
|
||||||
|
quantity: qtyNum,
|
||||||
|
unit: unitLower,
|
||||||
|
prep
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No quantity found, treat as name only
|
||||||
|
const name = parts.join(' ');
|
||||||
|
const commaIndex = name.indexOf(',');
|
||||||
|
let finalName = name;
|
||||||
|
let prep = null;
|
||||||
|
if (commaIndex !== -1) {
|
||||||
|
finalName = name.substring(0, commaIndex);
|
||||||
|
prep = name.substring(commaIndex + 1).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: finalName.trim(),
|
||||||
|
quantity: null,
|
||||||
|
unit: null,
|
||||||
|
prep
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearForm() {
|
||||||
|
// Clear all form inputs
|
||||||
|
const form = document.querySelector('form');
|
||||||
|
if (form) {
|
||||||
|
form.reset();
|
||||||
|
}
|
||||||
|
ingredientsText = '';
|
||||||
|
lastError = null;
|
||||||
|
lastSuccess = null;
|
||||||
|
formSubmitted = false;
|
||||||
|
}
|
||||||
|
|
||||||
async function onsubmit(event: SubmitEvent) {
|
async function onsubmit(event: SubmitEvent) {
|
||||||
|
event.preventDefault();
|
||||||
const form = event.target as HTMLFormElement;
|
const form = event.target as HTMLFormElement;
|
||||||
if (!form) return;
|
if (!form) return;
|
||||||
|
|
||||||
pending = true;
|
pending = true;
|
||||||
lastError = null;
|
lastError = null;
|
||||||
|
lastSuccess = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// native submit; progressive enhancement
|
// Parse ingredients on client side
|
||||||
|
const ingredientsRaw = ingredientsText.trim();
|
||||||
|
const parsedIngredients: Array<{
|
||||||
|
name: string;
|
||||||
|
quantity: number | null;
|
||||||
|
unit: string | null;
|
||||||
|
prep: string | null;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
if (ingredientsRaw) {
|
||||||
|
const lines = ingredientsRaw.split('\n').filter((line) => line.trim());
|
||||||
|
for (const line of lines) {
|
||||||
|
const parsed = parseIngredientLine(line.trim());
|
||||||
|
if (parsed) {
|
||||||
|
parsedIngredients.push(parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create FormData with parsed ingredients
|
||||||
|
const formData = new FormData(form);
|
||||||
|
|
||||||
|
// Remove the raw ingredients textarea
|
||||||
|
formData.delete('ingredients');
|
||||||
|
|
||||||
|
// Add parsed ingredients as JSON
|
||||||
|
formData.append('parsedIngredients', JSON.stringify(parsedIngredients));
|
||||||
|
|
||||||
|
// Submit to server
|
||||||
|
const response = await fetch('?/create', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
// Show success message
|
||||||
|
lastSuccess = 'Recipe created successfully!';
|
||||||
|
formSubmitted = true;
|
||||||
|
|
||||||
|
// Clear the form after a short delay
|
||||||
|
clearForm();
|
||||||
|
|
||||||
|
// Redirect to the new recipe immediately after showing success message
|
||||||
|
const result = await response.json();
|
||||||
|
console.log(result);
|
||||||
|
if (result.location) {
|
||||||
|
window.location.href = result.location;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const errorData = await response.json();
|
||||||
|
lastError = errorData.message || 'Failed to create recipe';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
lastError = 'An error occurred while creating the recipe';
|
||||||
|
console.error('Error creating recipe:', error);
|
||||||
} finally {
|
} finally {
|
||||||
// allow server navigation to take over
|
pending = false;
|
||||||
setTimeout(() => (pending = false), 400);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mx-auto my-6 max-w-3xl px-5">
|
<div class="mx-auto my-4 w-full max-w-2xl px-3 sm:my-6 sm:px-6 lg:max-w-4xl">
|
||||||
<h1 class="mb-5 text-2xl font-extrabold">New Recipe</h1>
|
<h1 class="mb-4 text-xl font-extrabold sm:mb-5 sm:text-2xl">New Recipe</h1>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
method="POST"
|
method="POST"
|
||||||
action="?/create"
|
action="?/create"
|
||||||
{onsubmit}
|
{onsubmit}
|
||||||
class="card grid gap-4 border border-base-200 bg-base-100 p-4 shadow-xl"
|
class="card relative grid gap-4 border border-base-200 bg-base-100 p-3 shadow-xl sm:p-6"
|
||||||
>
|
>
|
||||||
<div class="grid grid-cols-1 gap-3 md:grid-cols-3">
|
{#if pending}
|
||||||
<label class="form-control">
|
<div
|
||||||
|
class="absolute inset-0 z-10 flex items-center justify-center rounded-lg bg-base-100/80 backdrop-blur-sm"
|
||||||
|
>
|
||||||
|
<div class="text-center">
|
||||||
|
<span class="loading loading-lg loading-spinner text-primary"></span>
|
||||||
|
<p class="mt-2 text-base-content/70">Creating your recipe...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
|
<label class="form-control sm:col-span-2 lg:col-span-1">
|
||||||
<span class="label"><span class="label-text font-bold">Name</span></span>
|
<span class="label"><span class="label-text font-bold">Name</span></span>
|
||||||
<input
|
<input
|
||||||
name="name"
|
name="name"
|
||||||
required
|
required
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
inputmode="text"
|
inputmode="text"
|
||||||
class="input-bordered input input-lg"
|
class="input-bordered input w-full"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="form-control">
|
<label class="form-control">
|
||||||
<span class="label"><span class="label-text font-bold">Time</span></span>
|
<span class="label"><span class="label-text font-bold">Time</span></span>
|
||||||
<select name="time" class="select-bordered select select-lg">
|
<select name="time" class="select-bordered select w-full">
|
||||||
<option>Quick</option>
|
<option>Quick</option>
|
||||||
<option selected>Medium</option>
|
<option selected>Medium</option>
|
||||||
<option>Long</option>
|
<option>Long</option>
|
||||||
@ -48,7 +342,7 @@
|
|||||||
|
|
||||||
<label class="form-control">
|
<label class="form-control">
|
||||||
<span class="label"><span class="label-text font-bold">Station</span></span>
|
<span class="label"><span class="label-text font-bold">Station</span></span>
|
||||||
<select name="station" class="select-bordered select select-lg">
|
<select name="station" class="select-bordered select w-full">
|
||||||
<option>Garde Manger</option>
|
<option>Garde Manger</option>
|
||||||
<option selected>Pans</option>
|
<option selected>Pans</option>
|
||||||
<option>Grill</option>
|
<option>Grill</option>
|
||||||
@ -59,42 +353,103 @@
|
|||||||
<label class="form-control">
|
<label class="form-control">
|
||||||
<span class="label"><span class="label-text font-bold">Ingredients (one per line)</span></span
|
<span class="label"><span class="label-text font-bold">Ingredients (one per line)</span></span
|
||||||
>
|
>
|
||||||
|
|
||||||
<textarea
|
<textarea
|
||||||
name="ingredients"
|
name="ingredients"
|
||||||
rows="6"
|
rows="6"
|
||||||
placeholder="Eggs
|
placeholder="2 cups flour, sifted
|
||||||
Butter
|
3g salt
|
||||||
Salt"
|
500ml water"
|
||||||
class="textarea-bordered textarea textarea-lg"
|
class="textarea-bordered textarea w-full"
|
||||||
|
bind:value={ingredientsText}
|
||||||
></textarea>
|
></textarea>
|
||||||
</label>
|
<div class="label">
|
||||||
|
<span class="label-text-alt text-sm text-base-content/60">
|
||||||
|
<strong>Pattern:</strong> [quantity] [unit] ingredient name[, prep notes]<br />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if ingredientsText}
|
||||||
|
<div class="mt-3 rounded-lg bg-base-200 p-3">
|
||||||
|
<div class="mb-2 text-sm font-semibold text-base-content/70">Live Preview:</div>
|
||||||
|
{#each ingredientsText.split('\n').filter((line) => line.trim()) as line, i}
|
||||||
|
{#if line.trim()}
|
||||||
|
{@const parsed = parseIngredientLine(line.trim())}
|
||||||
|
{#if parsed}
|
||||||
|
<div class="mb-2 rounded border-l-4 border-primary bg-base-100 p-2">
|
||||||
|
<div class="mb-1 text-xs text-base-content/50">Line {i + 1}:</div>
|
||||||
|
<div class="flex flex-wrap gap-2 text-sm">
|
||||||
|
{#if parsed.quantity}
|
||||||
|
<span class="badge badge-sm badge-primary">Qty: {parsed.quantity}</span>
|
||||||
|
{/if}
|
||||||
|
{#if parsed.unit}
|
||||||
|
<span class="badge badge-sm badge-secondary">Unit: {parsed.unit}</span>
|
||||||
|
{/if}
|
||||||
|
<span class="badge badge-sm badge-accent">Name: {parsed.name}</span>
|
||||||
|
{#if parsed.prep}
|
||||||
|
<span class="badge badge-outline badge-sm">Prep: {parsed.prep}</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="tooltip tooltip-top"
|
||||||
|
data-tip="tsp, tbsp, cup(s), ml, l, g, kg, oz, lb, clove, piece, pc"
|
||||||
|
>
|
||||||
|
<strong class="cursor-help underline decoration-dotted">Valid units</strong>
|
||||||
|
</div>
|
||||||
<label class="form-control">
|
<label class="form-control">
|
||||||
<span class="label"><span class="label-text font-bold">Description</span></span>
|
<span class="label"><span class="label-text font-bold">Description</span></span>
|
||||||
<textarea name="description" rows="3" class="textarea-bordered textarea textarea-lg"
|
<textarea name="description" rows="3" class="textarea-bordered textarea w-full"></textarea>
|
||||||
></textarea>
|
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="form-control">
|
<label class="form-control">
|
||||||
<span class="label"><span class="label-text font-bold">Instructions</span></span>
|
<span class="label"><span class="label-text font-bold">Instructions</span></span>
|
||||||
<textarea name="instructions" rows="6" class="textarea-bordered textarea textarea-lg"
|
<textarea name="instructions" rows="6" class="textarea-bordered textarea w-full"></textarea>
|
||||||
></textarea>
|
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="mt-1 flex justify-end gap-3">
|
<div class="mt-2 flex flex-col gap-3 sm:flex-row sm:justify-end">
|
||||||
<a class="btn" href="/">Cancel</a>
|
<a class="btn order-2 w-full sm:order-1 sm:w-auto" href="/">Cancel</a>
|
||||||
<button
|
<button
|
||||||
class="btn font-extrabold btn-lg btn-primary"
|
class="btn order-1 w-full font-extrabold btn-primary sm:order-2 sm:w-auto"
|
||||||
type="submit"
|
type="submit"
|
||||||
name="/create"
|
name="/create"
|
||||||
disabled={pending}
|
disabled={pending}
|
||||||
>
|
>
|
||||||
{pending ? 'Saving…' : 'Save'}
|
{#if pending}
|
||||||
|
<span class="loading loading-sm loading-spinner"></span>
|
||||||
|
Creating Recipe…
|
||||||
|
{:else}
|
||||||
|
Create Recipe
|
||||||
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{#if lastError}
|
{#if lastError}
|
||||||
<p class="text-error">{lastError}</p>
|
<p class="text-error">{lastError}</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if lastSuccess}
|
||||||
|
<div class="mt-3 alert alert-success">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-6 w-6 shrink-0 stroke-current"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>{lastSuccess}</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
87
src/routes/recipe/new/+server.ts
Normal file
87
src/routes/recipe/new/+server.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import type { RequestHandler } from './$types';
|
||||||
|
import prisma from '$lib/server/prisma';
|
||||||
|
|
||||||
|
export const POST: RequestHandler = async ({ request }) => {
|
||||||
|
const formData = await request.formData();
|
||||||
|
|
||||||
|
const name = (formData.get('name') as string | null)?.trim() ?? '';
|
||||||
|
const description = (formData.get('description') as string | null)?.trim() || null;
|
||||||
|
const instructions = (formData.get('instructions') as string | null)?.trim() || null;
|
||||||
|
const time = ((formData.get('time') as string | null)?.trim() || 'Medium') as string;
|
||||||
|
const station = ((formData.get('station') as string | null)?.trim() || 'Pans') as string;
|
||||||
|
const parsedIngredientsRaw = formData.get('parsedIngredients') as string | null;
|
||||||
|
let parsedIngredients: Array<{ name: string; quantity: number | null; unit: string | null; prep: string | null }> = [];
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
return new Response(JSON.stringify({ type: 'error', message: 'Name is required' }), { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsedIngredientsRaw) {
|
||||||
|
try {
|
||||||
|
parsedIngredients = JSON.parse(parsedIngredientsRaw);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to parse ingredients JSON:', error);
|
||||||
|
return new Response(JSON.stringify({ type: 'error', message: 'Invalid ingredients format' }), { status: 400 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create recipe
|
||||||
|
const recipe = await prisma.recipe.create({
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
instructions,
|
||||||
|
time,
|
||||||
|
station
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (parsedIngredients.length > 0) {
|
||||||
|
// Upsert ingredients first
|
||||||
|
const ingredientNames = parsedIngredients.map(i => i.name).filter(Boolean);
|
||||||
|
const existingIngredients = await prisma.ingredient.findMany({
|
||||||
|
where: { name: { in: ingredientNames } }
|
||||||
|
});
|
||||||
|
|
||||||
|
const existingNames = new Set(existingIngredients.map(i => i.name));
|
||||||
|
|
||||||
|
const createIngredients = ingredientNames
|
||||||
|
.filter(n => !existingNames.has(n))
|
||||||
|
.map(n => ({ name: n }));
|
||||||
|
|
||||||
|
if (createIngredients.length) {
|
||||||
|
await prisma.ingredient.createMany({
|
||||||
|
data: createIngredients
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all ingredients with ids
|
||||||
|
const allIngredients = await prisma.ingredient.findMany({
|
||||||
|
where: { name: { in: ingredientNames } }
|
||||||
|
});
|
||||||
|
const ingredientMap = new Map(allIngredients.map(i => [i.name, i.id]));
|
||||||
|
|
||||||
|
// Bulk create recipeIngredient
|
||||||
|
const recipeIngredientsData = parsedIngredients
|
||||||
|
.filter(i => i.name)
|
||||||
|
.map(i => ({
|
||||||
|
recipeId: recipe.id,
|
||||||
|
ingredientId: ingredientMap.get(i.name)!,
|
||||||
|
quantity: i.quantity,
|
||||||
|
unit: i.unit,
|
||||||
|
prep: i.prep
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (recipeIngredientsData.length) {
|
||||||
|
await prisma.recipeIngredient.createMany({
|
||||||
|
data: recipeIngredientsData
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(JSON.stringify({
|
||||||
|
type: 'success',
|
||||||
|
status: 200,
|
||||||
|
location: `/recipe/${recipe.id}`
|
||||||
|
}), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user