Compare commits
No commits in common. "bd451c6cbb1b38af4488efc328212ec2cecf1dce" and "081145f900ff5dd41e9b951d3689c986e4d2e833" have entirely different histories.
bd451c6cbb
...
081145f900
25 changed files with 275 additions and 1022 deletions
|
|
@ -1,39 +0,0 @@
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container:
|
|
||||||
image: node:22-bullseye
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Install Docker
|
|
||||||
run: |
|
|
||||||
curl -fsSL https://get.docker.com -o get-docker.sh
|
|
||||||
sh get-docker.sh
|
|
||||||
|
|
||||||
- name: checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Login to forgejo
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: forgejo.fredzernia.com
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.PACKAGE_TOKEN }}
|
|
||||||
|
|
||||||
- name: build and push backend
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: backend/.
|
|
||||||
push: true
|
|
||||||
tags: forgejo.fredzernia.com/${{ env.FORGEJO_REPOSITORY }}_backend:latest
|
|
||||||
|
|
||||||
- name: build and push frontend
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: frontend/.
|
|
||||||
push: true
|
|
||||||
tags: forgejo.fredzernia.com/${{ env.FORGEJO_REPOSITORY }}_frontend:latest
|
|
||||||
26
.forgejo/workflows/ci.yml
Normal file
26
.forgejo/workflows/ci.yml
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
NODE_ENV: dev
|
||||||
|
container:
|
||||||
|
image: node:22-bullseye
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Build Frontend
|
||||||
|
working-directory: frontend
|
||||||
|
run: |
|
||||||
|
npm ci
|
||||||
|
npm run ci
|
||||||
|
|
||||||
|
- name: Build Backend
|
||||||
|
working-directory: backend
|
||||||
|
run: |
|
||||||
|
npm ci
|
||||||
|
npm run ci
|
||||||
25
README.md
25
README.md
|
|
@ -19,27 +19,4 @@
|
||||||
|
|
||||||
- Tailwind CSS
|
- Tailwind CSS
|
||||||
|
|
||||||
#### Infra/CI:
|
I have a production build of the app hosted in docker served with caddy on a vps running nixos that you can access here: https://recipe-app.fredzernia.com
|
||||||
|
|
||||||
This Forgejo instance is hosted on a VPS running NixOS\
|
|
||||||
The Forgejo Runner is hosted in a LXC container running on a local nix server\
|
|
||||||
The runner builds the containers and pushes them to this Forgejo registry\
|
|
||||||
In the frontend container I bundle the compiled src with Caddy to run as a standalone app\
|
|
||||||
The exposed frontend port can then be served via reverse proxy
|
|
||||||
|
|
||||||
#### Try it out:
|
|
||||||
|
|
||||||
https://recipe-app.fredzernia.com
|
|
||||||
|
|
||||||
#### Run it yourself:
|
|
||||||
|
|
||||||
```
|
|
||||||
mkdir recipe_app && cd recipe_app
|
|
||||||
wget https://forgejo.fredzernia.com/fred/recipe_app/raw/branch/main/docker-compose.yaml
|
|
||||||
wget https://forgejo.fredzernia.com/fred/recipe_app/raw/branch/main/example.env
|
|
||||||
mv example.env .env # Change these values if you want
|
|
||||||
docker compose pull
|
|
||||||
docker compose up db -d
|
|
||||||
docker compose run backend npx prisma migrate deploy
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,15 @@
|
||||||
FROM node:22-slim AS base
|
FROM node:22-slim
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
COPY package*.json tsconfig*.json prisma.config.ts ./
|
COPY package*.json ./
|
||||||
COPY prisma ./prisma
|
|
||||||
COPY src ./src
|
|
||||||
|
|
||||||
RUN apt-get update -y && \
|
RUN apt-get update -y && apt-get install -y openssl
|
||||||
apt-get install -y openssl && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
RUN npm install --omit=dev
|
RUN if [ "$NODE_ENV" = "dev" ]; then npm install; else npm install --omit=dev; fi
|
||||||
RUN npm run prisma:generate
|
|
||||||
|
|
||||||
FROM base AS builder
|
COPY . .
|
||||||
RUN npm install
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
|
|
||||||
FROM base
|
|
||||||
COPY --from=builder /app/dist ./dist
|
|
||||||
COPY --from=builder /app/prisma ./prisma
|
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
CMD ["npm", "run", "prod"]
|
CMD npm run $NODE_ENV
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
FROM node:22-slim
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY package*.json ./
|
|
||||||
|
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
EXPOSE 3000
|
|
||||||
|
|
||||||
CMD ["npm", "run", "dev"]
|
|
||||||
834
backend/package-lock.json
generated
834
backend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -4,30 +4,28 @@
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nodemon ./src/index.ts",
|
"dev": "nodemon ./src/index.ts",
|
||||||
"build": "tsc",
|
"production": "npx tsc && node ./dist/index.js",
|
||||||
"prisma:generate": "prisma generate",
|
"ci": "tsc"
|
||||||
"prod": "node ./dist/index.js"
|
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"description": "",
|
"description": "",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/adapter-pg": "7.2.0",
|
"@prisma/client": "^6.14.0",
|
||||||
"@prisma/client": "7.2.0",
|
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^17.0.1",
|
"dotenv": "^17.0.1",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"pg": "^8.17.2",
|
"knex": "^3.1.0",
|
||||||
"prisma": "7.2.0"
|
"pg": "^8.16.3",
|
||||||
|
"prisma": "^6.14.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"typescript": "^5.9.2",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"@types/node": "^24.2.1",
|
||||||
"@types/cors": "^2.8.19",
|
"@types/cors": "^2.8.19",
|
||||||
"@types/express": "^5.0.3",
|
"@types/express": "^5.0.3",
|
||||||
"@types/node": "^24.2.1",
|
"nodemon": "^3.1.10"
|
||||||
"@types/pg": "^8.16.0",
|
|
||||||
"nodemon": "^3.1.10",
|
|
||||||
"ts-node": "^10.9.2",
|
|
||||||
"typescript": "^5.9.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import 'dotenv/config';
|
|
||||||
import { defineConfig } from 'prisma/config';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
schema: 'prisma/schema.prisma',
|
|
||||||
migrations: {
|
|
||||||
path: 'prisma/migrations',
|
|
||||||
},
|
|
||||||
datasource: {
|
|
||||||
url: process.env['DATABASE_URL'],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
datasource db {
|
datasource db {
|
||||||
provider = "postgresql"
|
provider = "postgresql"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
generator client {
|
generator client {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,7 @@
|
||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import RecipeModel from "../models/recipeModel";
|
import RecipeModel from "../models/recipeModel";
|
||||||
import { PrismaClient } from "@prisma/client";
|
|
||||||
|
|
||||||
let model: RecipeModel;
|
const model = new RecipeModel();
|
||||||
|
|
||||||
export const initializeController = (prisma: PrismaClient) => {
|
|
||||||
model = new RecipeModel(prisma);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const test = async (req: Request, res: Response): Promise<void> => {
|
export const test = async (req: Request, res: Response): Promise<void> => {
|
||||||
console.log("test");
|
console.log("test");
|
||||||
|
|
@ -51,6 +46,9 @@ export const getRecipeById = async (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addRecipe = async (req: Request, res: Response): Promise<void> => {
|
export const addRecipe = async (req: Request, res: Response): Promise<void> => {
|
||||||
|
if (process.env.NODE_ENV === "demo") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
console.log(req.body);
|
console.log(req.body);
|
||||||
const createdRecipe = await model.addRecipe(req.body);
|
const createdRecipe = await model.addRecipe(req.body);
|
||||||
|
|
@ -70,6 +68,9 @@ export const updateRecipe = async (
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
console.log(req.body);
|
console.log(req.body);
|
||||||
const id = parseInt(req.params.id, 10);
|
const id = parseInt(req.params.id, 10);
|
||||||
|
if (process.env.NODE_ENV === "demo") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const updatedRecipe = await model.updateRecipe(req.body, id);
|
const updatedRecipe = await model.updateRecipe(req.body, id);
|
||||||
res.status(201).json(updatedRecipe);
|
res.status(201).json(updatedRecipe);
|
||||||
|
|
@ -83,6 +84,9 @@ export const updateRecipe = async (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setStars = async (req: Request, res: Response): Promise<void> => {
|
export const setStars = async (req: Request, res: Response): Promise<void> => {
|
||||||
|
if (process.env.NODE_ENV === "demo") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const id = parseInt(req.body.id, 10);
|
const id = parseInt(req.body.id, 10);
|
||||||
const stars = parseInt(req.body.stars, 10);
|
const stars = parseInt(req.body.stars, 10);
|
||||||
try {
|
try {
|
||||||
|
|
@ -101,6 +105,9 @@ export const deleteRecipe = async (
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
|
if (process.env.NODE_ENV === "demo") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const id = parseInt(req.body.id, 10);
|
const id = parseInt(req.body.id, 10);
|
||||||
try {
|
try {
|
||||||
await model.deleteRecipe(id);
|
await model.deleteRecipe(id);
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,24 @@
|
||||||
import "dotenv/config";
|
|
||||||
import express, { Express } from "express";
|
import express, { Express } from "express";
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
import { PrismaClient } from "@prisma/client";
|
import { PrismaClient } from "@prisma/client";
|
||||||
import { PrismaPg } from "@prisma/adapter-pg";
|
|
||||||
import appRoutes from "./routes/appRoutes";
|
import appRoutes from "./routes/appRoutes";
|
||||||
import { initializeController } from "./controllers/recipeController";
|
|
||||||
|
|
||||||
const app: Express = express();
|
const app: Express = express();
|
||||||
const port = process.env.PORT || 3000;
|
const port = process.env.PORT || 3000;
|
||||||
|
const prisma = new PrismaClient();
|
||||||
const adapter = new PrismaPg({
|
|
||||||
connectionString: process.env.DATABASE_URL,
|
|
||||||
});
|
|
||||||
|
|
||||||
const prisma = new PrismaClient({ adapter });
|
|
||||||
|
|
||||||
initializeController(prisma);
|
|
||||||
|
|
||||||
function setupMiddleware(app: Express) {
|
function setupMiddleware(app: Express) {
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use("/api", appRoutes);
|
app.use("/api", appRoutes);
|
||||||
}
|
}
|
||||||
|
|
||||||
setupMiddleware(app);
|
setupMiddleware(app);
|
||||||
|
|
||||||
|
// Start server
|
||||||
async function startServer() {
|
async function startServer() {
|
||||||
try {
|
try {
|
||||||
app.listen(port, () => {
|
app.listen(port);
|
||||||
console.log(`Server is running on http://localhost:${port}`);
|
console.log(`Server is running on http://localhost:${port}`);
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error starting the server:", error);
|
console.error("Error starting the server:", error);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ const logger = new Logger();
|
||||||
class RecipeModel {
|
class RecipeModel {
|
||||||
private prisma: PrismaClient;
|
private prisma: PrismaClient;
|
||||||
|
|
||||||
constructor(prisma: PrismaClient) {
|
constructor() {
|
||||||
this.prisma = prisma;
|
this.prisma = new PrismaClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllRecipes(): Promise<any[]> {
|
async getAllRecipes(): Promise<any[]> {
|
||||||
|
|
@ -40,18 +40,11 @@ class RecipeModel {
|
||||||
prep_minutes: recipe.prep_minutes,
|
prep_minutes: recipe.prep_minutes,
|
||||||
cook_minutes: recipe.cook_minutes,
|
cook_minutes: recipe.cook_minutes,
|
||||||
},
|
},
|
||||||
ingredients: recipe.recipeIngredients.map(
|
ingredients: recipe.recipeIngredients.map((ing) => ing.raw),
|
||||||
(ing: { raw: string | null }) => ing.raw,
|
steps: recipe.recipeSteps.map((step) => ({
|
||||||
),
|
step_number: step.step_number,
|
||||||
steps: recipe.recipeSteps.map(
|
instruction: step.instruction,
|
||||||
(step: {
|
})),
|
||||||
step_number: number | null;
|
|
||||||
instruction: string | null;
|
|
||||||
}) => ({
|
|
||||||
step_number: step.step_number ?? 0, // Default to 0 if null
|
|
||||||
instruction: step.instruction ?? "", // Default to empty string if null
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
logger.info("recipe page view", {
|
logger.info("recipe page view", {
|
||||||
recipe_id: data.details.id,
|
recipe_id: data.details.id,
|
||||||
|
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
services:
|
|
||||||
db:
|
|
||||||
container_name: recipes_db_dev
|
|
||||||
image: docker.io/library/postgres:17
|
|
||||||
restart: unless-stopped
|
|
||||||
env_file:
|
|
||||||
- .env
|
|
||||||
environment:
|
|
||||||
- POSTGRES_USER=${DB_USER}
|
|
||||||
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
|
||||||
- POSTGRES_DB=${DB_NAME}
|
|
||||||
ports:
|
|
||||||
- "${DB_PORT}:5432"
|
|
||||||
volumes:
|
|
||||||
- ./db:/var/lib/postgresql/data
|
|
||||||
backend:
|
|
||||||
container_name: recipes_backend_dev
|
|
||||||
restart: unless-stopped
|
|
||||||
build:
|
|
||||||
context: ./backend
|
|
||||||
dockerfile: Dockerfile.dev
|
|
||||||
ports:
|
|
||||||
- "${BACKEND_PORT}:3000"
|
|
||||||
volumes:
|
|
||||||
- ./backend:/app
|
|
||||||
- ./logs:/logs
|
|
||||||
environment:
|
|
||||||
- DB_USER=${DB_USER}
|
|
||||||
- DB_PASSWORD=${DB_PASSWORD}
|
|
||||||
- DB_NAME=${DB_NAME}
|
|
||||||
- DATABASE_URL=${DATABASE_URL}
|
|
||||||
frontend:
|
|
||||||
container_name: recipes_frontend_dev
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
- NODE_ENV=dev
|
|
||||||
build:
|
|
||||||
context: ./frontend
|
|
||||||
dockerfile: Dockerfile.dev
|
|
||||||
ports:
|
|
||||||
- "${FRONTEND_PORT}:80"
|
|
||||||
volumes:
|
|
||||||
- ./frontend:/app
|
|
||||||
|
|
@ -1,34 +1,47 @@
|
||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
container_name: recipes_db
|
container_name: recipes_postgres_${ID}
|
||||||
image: docker.io/library/postgres:17
|
image: docker.io/library/postgres:17
|
||||||
restart: unless-stopped
|
# restart: always
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_USER=${DB_USER}
|
- POSTGRES_USER=${DB_USER}
|
||||||
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
||||||
- POSTGRES_DB=${DB_NAME}
|
- POSTGRES_DB=${DB_NAME}
|
||||||
|
ports:
|
||||||
|
- "${DB_PORT}:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- ./db:/var/lib/postgresql/data
|
- ./db:/var/lib/postgresql/data
|
||||||
backend:
|
backend:
|
||||||
container_name: recipes_backend
|
image: recipes_backend
|
||||||
image: forgejo.fredzernia.com/fred/recipe_app_backend:latest
|
container_name: recipes_backend_${ID}
|
||||||
restart: unless-stopped
|
|
||||||
build:
|
build:
|
||||||
context: ./backend
|
context: ./backend
|
||||||
|
args:
|
||||||
|
NODE_ENV: ${NODE_ENV}
|
||||||
|
ports:
|
||||||
|
- "${BACKEND_PORT}:3000"
|
||||||
volumes:
|
volumes:
|
||||||
|
- ./backend:/usr/src/app
|
||||||
- ./logs:/logs
|
- ./logs:/logs
|
||||||
environment:
|
environment:
|
||||||
|
- NODE_ENV=${NODE_ENV}
|
||||||
- DB_USER=${DB_USER}
|
- DB_USER=${DB_USER}
|
||||||
- DB_PASSWORD=${DB_PASSWORD}
|
- DB_PASSWORD=${DB_PASSWORD}
|
||||||
- DB_NAME=${DB_NAME}
|
- DB_NAME=${DB_NAME}
|
||||||
- DATABASE_URL=${DATABASE_URL}
|
- DATABASE_URL=${PRISMA_DB_URL}
|
||||||
frontend:
|
frontend:
|
||||||
container_name: recipes_frontend
|
image: recipes_frontend
|
||||||
image: forgejo.fredzernia.com/fred/recipe_app_frontend:latest
|
container_name: recipes_frontend_${ID}
|
||||||
restart: unless-stopped
|
|
||||||
build:
|
build:
|
||||||
context: ./frontend
|
context: ./backend
|
||||||
|
args:
|
||||||
|
NODE_ENV: ${NODE_ENV}
|
||||||
ports:
|
ports:
|
||||||
- "${FRONTEND_PORT}:80"
|
- "${FRONTEND_PORT}:80"
|
||||||
|
volumes:
|
||||||
|
- ./frontend:/usr/src/app
|
||||||
|
- "$FRONTEND_BUILD_DIR:/usr/src/app/dist"
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=${NODE_ENV}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
DB_USER=recipe_app
|
|
||||||
DB_PASSWORD=password
|
|
||||||
DB_NAME=recipe_app
|
|
||||||
FRONTEND_PORT=8080
|
|
||||||
DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@db:5432/
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
:80 {
|
|
||||||
# Backend
|
|
||||||
handle /api/* {
|
|
||||||
reverse_proxy recipes_backend:3000
|
|
||||||
}
|
|
||||||
|
|
||||||
# Frontend
|
|
||||||
handle {
|
|
||||||
root * /srv/
|
|
||||||
try_files {path} /index.html
|
|
||||||
file_server
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +1,13 @@
|
||||||
FROM node:22-alpine AS builder
|
FROM node:22-alpine
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /usr/src/app
|
||||||
COPY package*.json tsconfig*.json postcss.config.js tailwind.config.js eslint.config.js vite.config.ts index.html ./
|
|
||||||
COPY src ./src
|
|
||||||
|
|
||||||
RUN apk update && apk upgrade
|
COPY package*.json ./
|
||||||
RUN npm install
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
FROM caddy:2.10
|
RUN if [ "$NODE_ENV" = "dev" ]; then npm install; else npm install --omit=dev; fi
|
||||||
COPY Caddyfile /etc/caddy/Caddyfile
|
|
||||||
COPY --from=builder /app/dist /srv
|
COPY . .
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
|
CMD npm run $NODE_ENV
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
FROM node:22-alpine
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY package*.json ./
|
|
||||||
|
|
||||||
RUN npm install;
|
|
||||||
|
|
||||||
EXPOSE 80
|
|
||||||
|
|
||||||
CMD ["npm", "run", "dev"]
|
|
||||||
20
frontend/package-lock.json
generated
20
frontend/package-lock.json
generated
|
|
@ -87,7 +87,6 @@
|
||||||
"integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
|
"integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.2.0",
|
"@ampproject/remapping": "^2.2.0",
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
|
|
@ -1472,7 +1471,6 @@
|
||||||
"integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==",
|
"integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.10.0"
|
"undici-types": "~7.10.0"
|
||||||
}
|
}
|
||||||
|
|
@ -1483,7 +1481,6 @@
|
||||||
"integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==",
|
"integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
|
|
@ -1544,7 +1541,6 @@
|
||||||
"integrity": "sha512-FuYgkHwZLuPbZjQHzJXrtXreJdFMKl16BFYyRrLxDhWr6Qr7Kbcu2s1Yhu8tsiMXw1S0W1pjfFfYEt+R604s+Q==",
|
"integrity": "sha512-FuYgkHwZLuPbZjQHzJXrtXreJdFMKl16BFYyRrLxDhWr6Qr7Kbcu2s1Yhu8tsiMXw1S0W1pjfFfYEt+R604s+Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.36.0",
|
"@typescript-eslint/scope-manager": "8.36.0",
|
||||||
"@typescript-eslint/types": "8.36.0",
|
"@typescript-eslint/types": "8.36.0",
|
||||||
|
|
@ -1796,7 +1792,6 @@
|
||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
|
|
@ -1997,7 +1992,6 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"caniuse-lite": "^1.0.30001726",
|
"caniuse-lite": "^1.0.30001726",
|
||||||
"electron-to-chromium": "^1.5.173",
|
"electron-to-chromium": "^1.5.173",
|
||||||
|
|
@ -2032,9 +2026,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001766",
|
"version": "1.0.30001727",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz",
|
||||||
"integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==",
|
"integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
|
@ -2326,7 +2320,6 @@
|
||||||
"integrity": "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==",
|
"integrity": "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
|
|
@ -3399,7 +3392,6 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.11",
|
"nanoid": "^3.3.11",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
|
|
@ -3576,7 +3568,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||||
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
|
|
@ -3586,7 +3577,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||||
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.26.0"
|
"scheduler": "^0.26.0"
|
||||||
},
|
},
|
||||||
|
|
@ -4104,7 +4094,6 @@
|
||||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
|
|
@ -4164,7 +4153,6 @@
|
||||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
|
|
@ -4257,7 +4245,6 @@
|
||||||
"integrity": "sha512-y2L5oJZF7bj4c0jgGYgBNSdIu+5HF+m68rn2cQXFbGoShdhV1phX9rbnxy9YXj82aS8MMsCLAAFkRxZeWdldrQ==",
|
"integrity": "sha512-y2L5oJZF7bj4c0jgGYgBNSdIu+5HF+m68rn2cQXFbGoShdhV1phX9rbnxy9YXj82aS8MMsCLAAFkRxZeWdldrQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.4.6",
|
"fdir": "^6.4.6",
|
||||||
|
|
@ -4348,7 +4335,6 @@
|
||||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host 0.0.0.0 --port 80",
|
"dev": "vite --host 0.0.0.0 --port 80",
|
||||||
"build": "tsc -b && vite build"
|
"production": "npx tsc -b && vite build",
|
||||||
|
"ci": "tsc -b && vite build",
|
||||||
|
"lint": "eslint ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
|
|
@ -13,8 +15,8 @@
|
||||||
"react-router-dom": "^7.6.3"
|
"react-router-dom": "^7.6.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.29.0",
|
|
||||||
"@types/node": "^24.2.0",
|
"@types/node": "^24.2.0",
|
||||||
|
"@eslint/js": "^9.29.0",
|
||||||
"@types/react": "^19.1.8",
|
"@types/react": "^19.1.8",
|
||||||
"@types/react-dom": "^19.1.6",
|
"@types/react-dom": "^19.1.6",
|
||||||
"@vitejs/plugin-react": "^4.5.2",
|
"@vitejs/plugin-react": "^4.5.2",
|
||||||
|
|
|
||||||
28
frontend/src/components/DemoModal.tsx
Normal file
28
frontend/src/components/DemoModal.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
interface DemoModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
closeModal: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DemoModal = ({ isOpen, onClose, closeModal }: DemoModalProps) => {
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`z-50 modal-overlay fixed top-0 left-0 w-full h-full bg-black bg-opacity-50 flex justify-center items-center`} onClick={onClose}>
|
||||||
|
<div className="modal-content bg-[var(--color-primaryBg)] p-12 rounded-md shadow-md" onClick={(e) => e.stopPropagation()}>
|
||||||
|
<div className="modal-msg">
|
||||||
|
<p>Thanks for checking out my app! Database write operations are disabled in demo mode.</p>
|
||||||
|
<p><a className="text-[var(--color-textLink)]" href="mailto:access@fredzernia.com">access@fredzernia.com</a> to request access to the production build</p>
|
||||||
|
<p>Find out more about this app <Link to={'/about'} className="text-blue-600">here</Link></p>
|
||||||
|
</div>
|
||||||
|
<div className="modal-buttons">
|
||||||
|
<button className="bg-[var(--color-buttonBg)] rounded-md m-4 pt-1 pb-1 pr-2 pl-2" onClick={closeModal}>OK</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DemoModal;
|
||||||
|
|
@ -1,12 +1,20 @@
|
||||||
|
import { useState } from "react";
|
||||||
import { addRecipe } from "../services/frontendApi.js";
|
import { addRecipe } from "../services/frontendApi.js";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import RecipeForm from "../components/RecipeForm.tsx";
|
import RecipeForm from "../components/RecipeForm.tsx";
|
||||||
|
import DemoModal from "../components/DemoModal.tsx";
|
||||||
import { type Recipe } from "../types/Recipe.ts"
|
import { type Recipe } from "../types/Recipe.ts"
|
||||||
|
|
||||||
function AddRecipe() {
|
function AddRecipe() {
|
||||||
|
const [showDemoModal, setShowDemoModal] = useState(false);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const addRecipeForm = async (recipeData: Recipe) => {
|
const addRecipeForm = async (recipeData: Recipe) => {
|
||||||
|
if (process.env.NODE_ENV === "demo") {
|
||||||
|
setShowDemoModal(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const data = await addRecipe(recipeData);
|
const data = await addRecipe(recipeData);
|
||||||
navigate(`/recipe/${data.id}`);
|
navigate(`/recipe/${data.id}`);
|
||||||
};
|
};
|
||||||
|
|
@ -14,6 +22,13 @@ function AddRecipe() {
|
||||||
return (
|
return (
|
||||||
<div className="add-recipe-card page-outer">
|
<div className="add-recipe-card page-outer">
|
||||||
<RecipeForm onSubmit={addRecipeForm} />
|
<RecipeForm onSubmit={addRecipeForm} />
|
||||||
|
{showDemoModal && (
|
||||||
|
<DemoModal
|
||||||
|
isOpen={showDemoModal}
|
||||||
|
onClose={() => setShowDemoModal(false)}
|
||||||
|
closeModal={() => setShowDemoModal(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
|
||||||
import { useParams, useNavigate } from "react-router-dom";
|
import { useParams, useNavigate } from "react-router-dom";
|
||||||
import { getRecipeById, updateRecipe } from "../services/frontendApi.js";
|
import { getRecipeById, updateRecipe } from "../services/frontendApi.js";
|
||||||
import RecipeForm from "../components/RecipeForm.tsx";
|
import RecipeForm from "../components/RecipeForm.tsx";
|
||||||
|
import DemoModal from "../components/DemoModal.tsx";
|
||||||
import { type Recipe } from "../types/Recipe.ts"
|
import { type Recipe } from "../types/Recipe.ts"
|
||||||
|
|
||||||
function EditRecipe() {
|
function EditRecipe() {
|
||||||
|
|
@ -9,6 +10,7 @@ function EditRecipe() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [recipe, setRecipe] = useState<Recipe>();
|
const [recipe, setRecipe] = useState<Recipe>();
|
||||||
|
const [showDemoModal, setShowDemoModal] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchRecipe = async () => {
|
const fetchRecipe = async () => {
|
||||||
|
|
@ -20,6 +22,11 @@ function EditRecipe() {
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
const updateRecipeForm = async (recipeData: Recipe) => {
|
const updateRecipeForm = async (recipeData: Recipe) => {
|
||||||
|
if (process.env.NODE_ENV === "demo") {
|
||||||
|
setShowDemoModal(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await updateRecipe(id, recipeData);
|
await updateRecipe(id, recipeData);
|
||||||
navigate(`/recipe/${id}`);
|
navigate(`/recipe/${id}`);
|
||||||
};
|
};
|
||||||
|
|
@ -29,6 +36,13 @@ function EditRecipe() {
|
||||||
{recipe && (
|
{recipe && (
|
||||||
<RecipeForm onSubmit={updateRecipeForm} initialData={recipe} />
|
<RecipeForm onSubmit={updateRecipeForm} initialData={recipe} />
|
||||||
)}
|
)}
|
||||||
|
{showDemoModal && (
|
||||||
|
<DemoModal
|
||||||
|
isOpen={showDemoModal}
|
||||||
|
onClose={() => setShowDemoModal(false)}
|
||||||
|
closeModal={() => setShowDemoModal(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
} from "../services/frontendApi.js";
|
} from "../services/frontendApi.js";
|
||||||
import { type Recipe } from "../types/Recipe";
|
import { type Recipe } from "../types/Recipe";
|
||||||
import Modal from "../components/Modal.tsx";
|
import Modal from "../components/Modal.tsx";
|
||||||
|
import DemoModal from "../components/DemoModal.tsx";
|
||||||
import StarRating from "../components/StarRating.tsx";
|
import StarRating from "../components/StarRating.tsx";
|
||||||
import TimeDisplay from "../components/TimeDisplay.tsx";
|
import TimeDisplay from "../components/TimeDisplay.tsx";
|
||||||
|
|
||||||
|
|
@ -21,6 +22,7 @@ function RecipePage() {
|
||||||
const [stars, setStars] = useState<number>(0);
|
const [stars, setStars] = useState<number>(0);
|
||||||
const [initialStars, setInitialStars] = useState<number | null>(null);
|
const [initialStars, setInitialStars] = useState<number | null>(null);
|
||||||
const [showConfirmModal, setShowConfirmModal] = useState(false);
|
const [showConfirmModal, setShowConfirmModal] = useState(false);
|
||||||
|
const [showDemoModal, setShowDemoModal] = useState(false);
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const isWebSource =
|
const isWebSource =
|
||||||
recipe && recipe.details && recipe.details.author
|
recipe && recipe.details && recipe.details.author
|
||||||
|
|
@ -36,8 +38,13 @@ function RecipePage() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const confirmDelete = () => {
|
const confirmDelete = () => {
|
||||||
|
if (process.env.NODE_ENV === "demo") {
|
||||||
|
closeModal();
|
||||||
|
setShowDemoModal(true);
|
||||||
|
} else {
|
||||||
handleDelete(recipe.details.id);
|
handleDelete(recipe.details.id);
|
||||||
closeModal();
|
closeModal();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -106,13 +113,13 @@ function RecipePage() {
|
||||||
<div className="flex relative justify-between">
|
<div className="flex relative justify-between">
|
||||||
<div className="invisible-buttons">
|
<div className="invisible-buttons">
|
||||||
<button
|
<button
|
||||||
onClick={() => { }}
|
onClick={() => {}}
|
||||||
className="invisible ar-button py-1 px-1 rounded mr-2 self-start"
|
className="invisible ar-button py-1 px-1 rounded mr-2 self-start"
|
||||||
>
|
>
|
||||||
🗑️
|
🗑️
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => { }}
|
onClick={() => {}}
|
||||||
className="invisible ar-button py-1 px-1 rounded self-start"
|
className="invisible ar-button py-1 px-1 rounded self-start"
|
||||||
>
|
>
|
||||||
🗑️
|
🗑️
|
||||||
|
|
@ -213,6 +220,13 @@ function RecipePage() {
|
||||||
confirmAction={confirmDelete}
|
confirmAction={confirmDelete}
|
||||||
cancelAction={closeModal}
|
cancelAction={closeModal}
|
||||||
/>
|
/>
|
||||||
|
{showDemoModal && (
|
||||||
|
<DemoModal
|
||||||
|
isOpen={showDemoModal}
|
||||||
|
onClose={() => setShowDemoModal(false)}
|
||||||
|
closeModal={() => setShowDemoModal(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
shell.nix
14
shell.nix
|
|
@ -1,14 +0,0 @@
|
||||||
{ pkgs ? import <nixpkgs> {} }:
|
|
||||||
pkgs.mkShell {
|
|
||||||
buildInputs = [
|
|
||||||
pkgs.prisma-engines
|
|
||||||
pkgs.prisma
|
|
||||||
];
|
|
||||||
shellHook = ''
|
|
||||||
export PKG_CONFIG_PATH="${pkgs.openssl.dev}/lib/pkgconfig"
|
|
||||||
export PRISMA_SCHEMA_ENGINE_BINARY="${pkgs.prisma-engines}/bin/schema-engine"
|
|
||||||
export PRISMA_QUERY_ENGINE_BINARY="${pkgs.prisma-engines}/bin/query-engine"
|
|
||||||
export PRISMA_QUERY_ENGINE_LIBRARY="${pkgs.prisma-engines}/lib/libquery_engine.node"
|
|
||||||
export PRISMA_FMT_BINARY="${pkgs.prisma-engines}/bin/prisma-fmt"
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue