├── .dockerignore ├── .env ├── .env.example ├── .eslintrc.json ├── .gitignore ├── .wakatime-project ├── Dockerfile ├── README.md ├── app ├── about │ └── page.tsx ├── api │ ├── file │ │ └── [name] │ │ │ └── route.ts │ ├── recipes │ │ ├── get-single-recipe │ │ │ └── route.ts │ │ └── route.ts │ ├── route.ts │ └── search │ │ └── route.ts ├── contact │ └── page.tsx ├── favicon.ico ├── globals.css ├── layout.tsx ├── not-found.tsx ├── page.tsx ├── recipes │ ├── [id] │ │ └── page.tsx │ ├── create │ │ └── page.tsx │ ├── page.tsx │ └── search │ │ └── [q] │ │ └── page.tsx └── sitemap.ts ├── components ├── Footer.tsx ├── Header.tsx ├── HeaderBottom.tsx ├── LandingSection.tsx ├── LatestRecipes.tsx ├── RecipeCard.tsx ├── Search.tsx ├── SingleRecipeMain.tsx ├── SingleRecipeTopSection.tsx └── ui │ ├── BackButton.tsx │ ├── Button.tsx │ └── Skeleton.tsx ├── denizpaz.ir.conf ├── docker-compose.yml ├── environment.d.ts ├── lib ├── data.ts └── mongodb.ts ├── models ├── index.ts └── recipe.ts ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── contactus.jpg ├── facebook-2.svg ├── facebook.png ├── header-recipe.png ├── instagram.png ├── landing-header-image.png ├── linkedin.png ├── logo.png ├── me.png ├── recipe-header.jpg ├── search-page.jpg └── twitter.png ├── seeder.ts ├── styles ├── fonts.ts └── fonts │ ├── ttf │ ├── Vazirmatn-Black.ttf │ ├── Vazirmatn-Bold.ttf │ ├── Vazirmatn-ExtraBold.ttf │ ├── Vazirmatn-ExtraLight.ttf │ ├── Vazirmatn-Light.ttf │ ├── Vazirmatn-Medium.ttf │ ├── Vazirmatn-Regular.ttf │ ├── Vazirmatn-SemiBold.ttf │ └── Vazirmatn-Thin.ttf │ ├── variable │ └── Vazirmatn[wght].ttf │ └── webfonts │ ├── Vazirmatn-Black.woff2 │ ├── Vazirmatn-Bold.woff2 │ ├── Vazirmatn-ExtraBold.woff2 │ ├── Vazirmatn-ExtraLight.woff2 │ ├── Vazirmatn-Light.woff2 │ ├── Vazirmatn-Medium.woff2 │ ├── Vazirmatn-Regular.woff2 │ ├── Vazirmatn-SemiBold.woff2 │ ├── Vazirmatn-Thin.woff2 │ └── Vazirmatn[wght].woff2 ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.seeder.json └── types ├── Components.ts ├── Generic.ts ├── Recipe.ts └── mongodb.d.ts /.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .dockerignore 3 | node_modules 4 | npm-debug.log 5 | README.md 6 | .next 7 | .git -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # DO NOT ADD SECRETS TO THIS FILE. This is a good place for defaults. 2 | # If you want to add secrets use `.env.local` instead. 3 | 4 | ENV_VARIABLE=production_server_only_variable 5 | NEXT_PUBLIC_ENV_VARIABLE=production_public_variable 6 | NEXT_PUBLIC_DB_LOCAL_URL="mongodb://localhost:27017/denizpaz" 7 | PORT=3000 8 | STORE_PATH=upload -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # DO NOT ADD SECRETS TO THIS FILE. This is a good place for defaults. 2 | # If you want to add secrets use `.env.local` instead. 3 | 4 | ENV_VARIABLE=production_server_only_variable 5 | NEXT_PUBLIC_ENV_VARIABLE=production_public_variable 6 | NEXT_PUBLIC_DB_LOCAL_URL="mongodb://localhost:27017/denizpaz" 7 | PORT=3000 8 | STORE_PATH=upload -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | .env 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | /data 37 | public/uploads/recipes/* 38 | upload/* -------------------------------------------------------------------------------- /.wakatime-project: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ehsanghaffar/nextjs-appdir-docker/a7b7684aea6905e1155d3fe7901882ac43014a1f/.wakatime-project -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine 2 | 3 | RUN mkdir -p /app 4 | 5 | WORKDIR /app 6 | 7 | COPY package.json /app 8 | 9 | RUN npm install -g next 10 | 11 | RUN npm install 12 | 13 | COPY . /app 14 | 15 | RUN mkdir -p /app/.next/cache/images 16 | VOLUME /app/.next/cache/images 17 | 18 | RUN npm run build 19 | 20 | ENV DB_LOCAL_URL=mongodb://mongo:27017/denizpaz 21 | 22 | EXPOSE 3000 23 | 24 | CMD npm run start 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Next.js App with Docker and MongoDB Integration 2 | 3 | This repository contains a Next.js web application that utilizes various features, including appdir, Docker for containerization, MongoDB for data storage, and custom ul components. 4 | 5 | ## Features 6 | 7 | - **Next.js Appdir Feature:** The Next.js app in this repository is organized using the appdir feature, allowing for a clean and structured project layout. 8 | 9 | - **Docker Support:** The application is containerized using Docker, which simplifies the setup process and ensures consistent deployment across different environments. 10 | 11 | - **MongoDB Integration:** MongoDB is used as the database for this application, enabling efficient and scalable data storage for the web application. 12 | 13 | - **Tailwindcss Components:** The repository includes Tailwindcss styles for components that can be easily reused throughout the application. 14 | 15 | ## Prerequisites 16 | 17 | Before you begin, make sure you have the following dependencies installed on your system: 18 | 19 | - Node.js and npm (Node Package Manager) 20 | - Docker 21 | 22 | ## Getting Started 23 | 24 | Follow the steps below to set up the Next.js app on your local machine: 25 | 26 | 1. Clone this repository to your local machine using the following command: 27 | 28 | ```bash 29 | git clone https://github.com/ehsanghaffar/nextjs-appdir-docker 30 | ``` 31 | 32 | 2. Navigate to the project directory: 33 | 34 | ```bash 35 | cd nextjs-appdir-docker 36 | ``` 37 | 38 | 3. Install the required dependencies: 39 | 40 | ```bash 41 | npm install 42 | ``` 43 | 44 | 4. Create a `.env` file in the root directory and provide the necessary environment variables: 45 | 46 | ``` 47 | MONGODB_URI=mongodb://your-mongodb-uri 48 | ``` 49 | 50 | 5. Run the Next.js development server: 51 | 52 | ```bash 53 | npm run dev 54 | ``` 55 | 56 | The application should now be running on `http://localhost:3000`. 57 | 58 | ## Docker Deployment 59 | 60 | To deploy the application using Docker, follow these steps: 61 | 62 | 1. Ensure Docker is installed and running on your system. 63 | 64 | 2. Build and run the Docker image using the provided Docker compose: 65 | 66 | ```bash 67 | docker compose up -d 68 | ``` 69 | 70 | The application should now be accessible at `http://localhost:3000`. 71 | 72 | ## MongoDB Configuration 73 | 74 | Make sure you have a running MongoDB instance and obtain the connection URI. Replace the `MONGODB_URI` in the `.env` file with your MongoDB connection string. 75 | 76 | ## Custom UL Components 77 | 78 | The repository includes custom `ul` (unordered list) components that can be found in the `components` directory. You can easily use these components in your pages to display lists. 79 | 80 | ```jsx 81 | import React from 'react'; 82 | import { CustomUl } from '../components'; 83 | 84 | const YourPage = () => { 85 | return ( 86 |
87 |

Your Page Title

88 | 89 |
90 | ); 91 | }; 92 | 93 | export default YourPage; 94 | ``` 95 | 96 | Feel free to explore the code and make any modifications or enhancements you need for your project. 97 | 98 | ## License 99 | 100 | This project is licensed under the [MIT License](LICENSE). 101 | 102 | --- 103 | 104 | Happy coding! If you have any questions or need further assistance, feel free to reach out. 105 | -------------------------------------------------------------------------------- /app/about/page.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const AboutUs: React.FC = () => { 4 | return ( 5 |
6 |
7 |
8 |

About Us

9 | 10 |

11 | Welcome to Foodie Delights - Your Go-To Recipe App! 12 |

13 |
14 |
15 | me 16 |

Cooker

17 |

18 | At Foodie Delights, we believe that food brings people together, creates memories, and sparks joy. Our mission is to inspire and empower both aspiring and seasoned chefs to explore the world of culinary delights right from the comfort of their homes. Whether you are a cooking enthusiast or just getting started in the kitchen, Foodie Delights has something for everyone. 19 |

20 |
21 |

22 | Our Recipe Collection 23 |

24 |

25 | We take pride in curating a diverse and extensive recipe collection that spans cuisines from all corners of the globe. Our team of passionate food experts works tirelessly to bring you the most delicious and authentic recipes that are easy to follow and recreate. From traditional family favorites to contemporary twists on classic dishes, there is always something new to explore. 26 |

27 |
28 |
29 |
30 |
31 | ); 32 | }; 33 | 34 | export default AboutUs; 35 | -------------------------------------------------------------------------------- /app/api/file/[name]/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import { existsSync } from "fs"; 3 | import fs from "fs/promises"; 4 | import path from "path"; 5 | import mime from "mime/lite"; 6 | 7 | export async function GET( 8 | req: NextRequest, 9 | { params }: { params: { name: string } } 10 | ) { 11 | const filePath = path.join( 12 | process.cwd(), 13 | process.env.STORE_PATH!, 14 | params.name 15 | ); 16 | if (!existsSync(filePath)) { 17 | return NextResponse.json({ msg: "Not found" }, { status: 404 }); 18 | } 19 | 20 | const mimeType = mime.getType(filePath); 21 | const fileStat = await fs.stat(filePath); 22 | 23 | const file = await fs.readFile(filePath); 24 | 25 | const headers: [string, string][] = [ 26 | ["Content-Length", fileStat.size.toString()], 27 | ]; 28 | if (mimeType) { 29 | headers.push(["Content-Type", mimeType]); 30 | } 31 | return new NextResponse(file, { 32 | headers, 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /app/api/recipes/get-single-recipe/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | import { Recipe } from "../../../../models"; 3 | 4 | export const dynamic = 'force-dynamic' 5 | export async function GET(req: Request) { 6 | try { 7 | const { searchParams } = new URL(req.url); 8 | const id = searchParams.get('id') 9 | 10 | const recipe = await Recipe.findOne({ 11 | _id: id 12 | }); 13 | 14 | if (await recipe) { 15 | return NextResponse.json({ status: 200, data: recipe }); 16 | } else { 17 | return NextResponse.json({ status: 204, success: false, message: 'No recipe found.' }); 18 | } 19 | } catch (error) { 20 | console.log('Error in getting recipe by id:', error); 21 | return NextResponse.json({ status: 500, success: false, message: error }); 22 | } 23 | } -------------------------------------------------------------------------------- /app/api/recipes/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | import { connectToDatabase } from "../../../lib/mongodb"; 3 | import { Recipe } from "../../../models"; 4 | import { existsSync } from "fs"; 5 | import fs from "fs/promises"; 6 | import path from "path"; 7 | 8 | connectToDatabase(); 9 | 10 | export async function GET() { 11 | try { 12 | const recipes = Recipe.find(); 13 | return NextResponse.json((await recipes).reverse()); 14 | } catch (error) { 15 | console.log(error); 16 | return NextResponse.json('error', { 17 | status: 500 18 | }); 19 | } 20 | } 21 | 22 | export async function POST(req: Request) { 23 | const data = await req.formData(); 24 | const file: File | null = data.get('photo') as unknown as File; 25 | 26 | const fileArrayBuffer = await file.arrayBuffer(); 27 | const destinationDirPath = path.join(process.cwd(), process.env.STORE_PATH!); 28 | 29 | if (!existsSync(destinationDirPath)) { 30 | await fs.mkdir(destinationDirPath, { recursive: true }); 31 | } 32 | 33 | let name = data.get('name') 34 | var fileExtension = file.name.split('.').pop(); 35 | let filename = `${name}.${fileExtension}` 36 | while (existsSync(path.join(destinationDirPath, filename))) { 37 | filename = `(1)` + filename; 38 | } 39 | await fs.writeFile( 40 | path.join(destinationDirPath, filename), 41 | Buffer.from(fileArrayBuffer) 42 | ); 43 | try { 44 | const newRecipe = { 45 | name: data.get('name'), 46 | description: data.get('description'), 47 | ingredients: JSON.parse(data.get('ingredients') as string), 48 | steps: data.get('steps'), 49 | photo: `/api/file/${filename}` 50 | } 51 | 52 | const recipe = new Recipe(newRecipe); 53 | const save = await recipe.save(); 54 | return NextResponse.json({ status: 200, data: save }); 55 | } catch (error) { 56 | console.log(error); 57 | return NextResponse.json('error', { 58 | status: 500, 59 | }); 60 | } 61 | 62 | } 63 | 64 | export async function DELETE(req: any) { 65 | const { id } = req.query; 66 | try { 67 | console.log("id", id) 68 | const recipe = await Recipe.findByIdAndRemove(id) 69 | console.log("re", recipe) 70 | return NextResponse.json({ status: 200, data: recipe }) 71 | } catch (error) { 72 | return NextResponse.json({ status: 500, success: false, message: error }); 73 | } 74 | } -------------------------------------------------------------------------------- /app/api/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | 3 | export async function GET() { 4 | const user = { 5 | name: "ehsan" 6 | } 7 | 8 | return NextResponse.json({ user }); 9 | } -------------------------------------------------------------------------------- /app/api/search/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | import { connectToDatabase } from "../../../lib/mongodb"; 3 | import { Recipe } from "../../../models"; 4 | 5 | export const dynamic = 'force-dynamic' 6 | connectToDatabase() 7 | 8 | export async function GET(req: Request) { 9 | try { 10 | const { searchParams } = new URL(req.url); 11 | const query = searchParams.get('q') 12 | const recipes = await Recipe.find() 13 | 14 | const recipesData = recipes.filter((recipe) => { 15 | return recipe["name"].includes(query) 16 | }) 17 | return NextResponse.json(recipesData); 18 | } catch (error) { 19 | console.log(error) 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /app/contact/page.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Contact = () => { 4 | return ( 5 | <> 6 |
7 |
8 |
9 |
10 |

11 | Contact Us 12 |

13 |

14 | For any app-related inquiries, technical issues, or general 15 | assistance, our customer support team is ready to lend a helping 16 | hand. You can reach us via email at info@ehsanghaffarii.ir or 17 | through the contact form below. 18 |

19 |
20 |
21 | contactus 26 |
27 |
28 |
29 |
30 | 31 | ); 32 | }; 33 | 34 | export default Contact; 35 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ehsanghaffar/nextjs-appdir-docker/a7b7684aea6905e1155d3fe7901882ac43014a1f/app/favicon.ico -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } */ 18 | 19 | 20 | html { 21 | line-height: 1.15; 22 | -webkit-text-size-adjust: 100%; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | } 28 | 29 | main { 30 | display: block; 31 | } 32 | 33 | h1 { 34 | font-size: 2em; 35 | margin: 0.67em 0; 36 | } 37 | 38 | hr { 39 | box-sizing: content-box; 40 | height: 0; 41 | overflow: visible; 42 | } 43 | 44 | 45 | a { 46 | background-color: transparent; 47 | } 48 | 49 | abbr[title] { 50 | border-bottom: none; 51 | text-decoration: underline; 52 | text-decoration: underline dotted; 53 | } 54 | 55 | b, 56 | strong { 57 | font-weight: bolder; 58 | } 59 | 60 | 61 | small { 62 | font-size: 80%; 63 | } 64 | 65 | sub, 66 | sup { 67 | font-size: 75%; 68 | line-height: 0; 69 | position: relative; 70 | vertical-align: baseline; 71 | } 72 | 73 | sub { 74 | bottom: -0.25em; 75 | } 76 | 77 | sup { 78 | top: -0.5em; 79 | } 80 | 81 | img { 82 | border-style: none; 83 | } 84 | 85 | button, 86 | input, 87 | optgroup, 88 | select, 89 | textarea { 90 | /* font-family: inherit; */ 91 | font-size: 100%; 92 | line-height: 1.15; 93 | margin: 0; 94 | } 95 | 96 | button, 97 | input { 98 | overflow: visible; 99 | } 100 | 101 | button, 102 | select { 103 | text-transform: none; 104 | } 105 | 106 | button, 107 | [type="button"], 108 | [type="reset"], 109 | [type="submit"] { 110 | -webkit-appearance: button; 111 | } 112 | 113 | button::-moz-focus-inner, 114 | [type="button"]::-moz-focus-inner, 115 | [type="reset"]::-moz-focus-inner, 116 | [type="submit"]::-moz-focus-inner { 117 | border-style: none; 118 | padding: 0; 119 | } 120 | 121 | button:-moz-focusring, 122 | [type="button"]:-moz-focusring, 123 | [type="reset"]:-moz-focusring, 124 | [type="submit"]:-moz-focusring { 125 | outline: 1px dotted ButtonText; 126 | } 127 | 128 | fieldset { 129 | padding: 0.35em 0.75em 0.625em; 130 | } 131 | 132 | legend { 133 | box-sizing: border-box; 134 | color: inherit; 135 | display: table; 136 | max-width: 100%; 137 | padding: 0; 138 | /* 3 */ 139 | white-space: normal; 140 | } 141 | 142 | progress { 143 | vertical-align: baseline; 144 | } 145 | 146 | textarea { 147 | overflow: auto; 148 | } 149 | 150 | [type="checkbox"], 151 | [type="radio"] { 152 | box-sizing: border-box; 153 | padding: 0; 154 | } 155 | 156 | [type="number"]::-webkit-inner-spin-button, 157 | [type="number"]::-webkit-outer-spin-button { 158 | height: auto; 159 | } 160 | 161 | [type="search"]::-webkit-search-decoration { 162 | -webkit-appearance: none; 163 | } 164 | 165 | ::-webkit-file-upload-button { 166 | -webkit-appearance: button; 167 | font: inherit; 168 | } 169 | 170 | details { 171 | display: block; 172 | } 173 | 174 | summary { 175 | display: list-item; 176 | } 177 | 178 | template { 179 | display: none; 180 | } 181 | 182 | [hidden] { 183 | display: none; 184 | } 185 | 186 | /* Start Project */ 187 | 188 | html { 189 | box-sizing: border-box; 190 | } 191 | 192 | *, 193 | *:before, 194 | *:after { 195 | box-sizing: inherit; 196 | } 197 | 198 | .container { 199 | max-width: 80rem; 200 | margin-right: auto; 201 | margin-left: auto; 202 | padding-right: 1rem; 203 | padding-left: 1rem; 204 | } 205 | 206 | .section-title { 207 | font-size: 3rem; 208 | color: #e32a22; 209 | text-align: center; 210 | } 211 | 212 | .section-des { 213 | color: #525c60; 214 | font-size: 1.35rem; 215 | text-align: center; 216 | padding-bottom: 2rem; 217 | } 218 | 219 | /* header nav bar */ 220 | 221 | #header-top { 222 | background-color: #f9f8f8; 223 | } 224 | 225 | .header-navbar { 226 | display: flex; 227 | justify-content: space-between; 228 | align-items: center; 229 | padding: 0.5rem 0; 230 | } 231 | 232 | .navbar-img { 233 | max-width: 10rem; 234 | max-height: 4rem; 235 | } 236 | 237 | .menu { 238 | display: flex; 239 | list-style-type: none; 240 | } 241 | 242 | .menu li { 243 | padding: 0 1.5rem; 244 | } 245 | 246 | .menu li:not(:last-child) { 247 | border-right: 0.1rem solid #86898a98; 248 | } 249 | 250 | .menu li a { 251 | transition: all 0.1s 252 | } 253 | 254 | .menu li a:hover { 255 | color: #e32a22; 256 | } 257 | 258 | .menu-links { 259 | text-decoration: none; 260 | color: #274C5B; 261 | font-size: 1.3rem; 262 | font-weight: bold; 263 | } 264 | 265 | /* main header */ 266 | 267 | #header-bottom { 268 | display: flex; 269 | justify-content: space-around; 270 | padding: 4rem 0; 271 | } 272 | 273 | .header-right { 274 | padding-right: 8rem; 275 | /* background-image: url(/images/bobl.png); */ 276 | background-position: center; 277 | background-repeat: no-repeat; 278 | 279 | 280 | } 281 | 282 | .header-des { 283 | color: #525c60; 284 | font-size: 1.35rem; 285 | font-weight: 600; 286 | } 287 | 288 | 289 | .header-link:hover { 290 | background-color: #12232b; 291 | } 292 | 293 | .header-link-img { 294 | transform: rotate(180deg); 295 | width: 1.7rem; 296 | padding-left: 0.4rem; 297 | } 298 | 299 | .header-left-img { 300 | max-width: 35rem; 301 | } 302 | 303 | /* recipes section */ 304 | 305 | #recipes { 306 | background-color: #F9f8f8; 307 | padding: 1.5rem 0; 308 | text-align: center; 309 | } 310 | 311 | #recipes .section-des { 312 | margin: 0; 313 | } 314 | 315 | .recipes { 316 | display: flex; 317 | justify-content: space-between; 318 | flex-wrap: wrap; 319 | padding: 2rem 2rem; 320 | } 321 | 322 | .recipe { 323 | padding: 1rem 1rem 2rem 1rem; 324 | margin: 2rem 1rem; 325 | border-radius: 1rem; 326 | background-color: #ffffff; 327 | box-shadow: 2px 5px 4px 2px rgba(0, 0, 0, 0.2); 328 | transition: all 0.3s; 329 | } 330 | 331 | .recipe:hover { 332 | transform: translateY(-0.3rem); 333 | } 334 | 335 | .recipe-img { 336 | max-width: 14rem; 337 | height: auto; 338 | border-radius: 1rem; 339 | } 340 | 341 | .recipe-des { 342 | text-align: center; 343 | } 344 | 345 | .recipe-title { 346 | color: #274C5B; 347 | font-weight: bold; 348 | padding-bottom: 0.7rem; 349 | } 350 | 351 | .recipe-link { 352 | text-decoration: none; 353 | font-size: 1rem; 354 | color: #fff; 355 | background-color: #274C5B; 356 | padding: 0.5rem 2rem; 357 | border-radius: 0.5rem; 358 | margin-top: 1rem; 359 | transition: all 0.2s; 360 | } 361 | 362 | .recipe-link:hover { 363 | background-color: #12232b; 364 | } 365 | /* 366 | .recipe-link-img { 367 | transform: rotate(180deg); 368 | width: 1.3rem; 369 | padding-left: 0.4rem; 370 | } */ 371 | 372 | .all-recipe-link { 373 | font-size: 1.7rem; 374 | color: #fff; 375 | background-color: #274C5B; 376 | padding: 1rem 2rem; 377 | border-radius: 0.8rem; 378 | cursor: pointer; 379 | text-decoration: none; 380 | margin: 3rem auto; 381 | transition: all 0.2s; 382 | } 383 | 384 | .all-recipe-link:hover { 385 | background-color: #12232b; 386 | } 387 | 388 | .all-recipe-link-img { 389 | width: 2rem; 390 | } 391 | 392 | 393 | /* about us section */ 394 | 395 | /* #aboutus { 396 | background-image: url(/images/aboutus.jpg); 397 | background-repeat: no-repeat; 398 | background-size: cover; 399 | height: 100vh; 400 | padding: 1.5rem 0; 401 | } */ 402 | 403 | #aboutus .section-title { 404 | margin: 0; 405 | } 406 | 407 | .aboutus-info { 408 | text-align: center; 409 | max-width: 35rem; 410 | margin: 0 auto; 411 | } 412 | 413 | .aboutus-info-title { 414 | font-weight: bold; 415 | font-size: 2rem; 416 | color: #274C5B; 417 | margin: 0.5rem 0 418 | } 419 | 420 | .aboutus-info-info { 421 | color: #525c60; 422 | font-size: 1.3rem; 423 | padding-bottom: 0.5rem; 424 | } 425 | 426 | .aboutus-counts { 427 | display: flex; 428 | justify-content: space-around; 429 | padding: 2rem 8rem; 430 | align-items: center; 431 | text-align: center; 432 | } 433 | 434 | .aboutus-count { 435 | background-color: #F1F1F1; 436 | width: 12rem; 437 | height: 12rem; 438 | border: 0.15rem solid #7EB693; 439 | border-radius: 10rem; 440 | display: flex; 441 | flex-direction: column; 442 | justify-content: center; 443 | transition: all 0.3s; 444 | } 445 | 446 | .aboutus-count:hover { 447 | transform: translateY(-0.3rem); 448 | } 449 | 450 | 451 | 452 | .aboutus-count-num { 453 | color: #274C5B; 454 | font-size: 3rem; 455 | font-weight: bold; 456 | margin: 0; 457 | } 458 | 459 | .aboutus-count-des { 460 | color: #274C5B; 461 | font-size: 1.1rem; 462 | font-weight: bold; 463 | margin: 0; 464 | } 465 | 466 | /* contactus section */ 467 | 468 | #contactus { 469 | padding: 5.5rem 0; 470 | background-color: #f9f8f8; 471 | } 472 | 473 | .contactus { 474 | display: flex; 475 | justify-content: space-between; 476 | } 477 | 478 | .contactus-right .section-title { 479 | text-align: right; 480 | } 481 | 482 | 483 | .contactus-info { 484 | display: flex; 485 | align-items: center; 486 | margin-bottom: 2rem; 487 | } 488 | 489 | .contactus-info-info { 490 | margin-right: 0.8rem; 491 | } 492 | 493 | .contactus-info-info-title { 494 | margin: 0; 495 | padding-bottom: 0.5rem; 496 | font-weight: bold; 497 | font-size: 1.5rem; 498 | color: #274C5B; 499 | margin: 0; 500 | padding-bottom: 0.4rem; 501 | } 502 | 503 | .contactus-info-info-des { 504 | margin: 0; 505 | color: #525c60; 506 | transition: all 0.2s; 507 | } 508 | 509 | .contactus-info-info-des:hover { 510 | font-weight: bold; 511 | } 512 | 513 | 514 | .contactus-info-image { 515 | background-color: #F4F4F4; 516 | padding: 1rem; 517 | border-radius: 0.7rem; 518 | } 519 | 520 | .contactus-right-info-img { 521 | width: 2.5rem; 522 | } 523 | 524 | /* subscribe section */ 525 | 526 | #subscribe { 527 | padding: 5rem 0 10rem 0; 528 | } 529 | 530 | 531 | /* 532 | .subscribe-img { 533 | background-image: url(/images/subsc.jpg); 534 | background-size: cover; 535 | height: 20rem; 536 | border-radius: 5rem; 537 | } */ 538 | 539 | .subscribe-input { 540 | border: none; 541 | outline: none; 542 | width: 21rem; 543 | padding: 1.2rem 0.8rem; 544 | border-radius: 1.2rem; 545 | text-align: left; 546 | } 547 | 548 | .subscribe-input::placeholder { 549 | color: #585e612c; 550 | } 551 | 552 | .subscribe-btn { 553 | border: none; 554 | outline: none; 555 | background-color: #274C5B; 556 | color: #fff; 557 | padding: 1.2rem 3rem; 558 | border-radius: 1.2rem; 559 | margin-top: 7.7rem; 560 | margin-right: 10rem; 561 | cursor: pointer; 562 | transition: all 0.2s; 563 | } 564 | 565 | .subscribe-btn:hover { 566 | background-color: #12232b; 567 | } 568 | 569 | 570 | 571 | /* footer section */ 572 | 573 | #footer { 574 | position: relative; 575 | background-color: #f9f8f8; 576 | } 577 | 578 | /* .footer .svg-bg { 579 | position: absolute; 580 | z-index: -1; 581 | bottom: 23.8rem; 582 | width: 100%; 583 | } */ 584 | 585 | .footer { 586 | display: flex; 587 | justify-content: space-around; 588 | padding: 3rem 0; 589 | } 590 | 591 | .footer-title { 592 | color: #e32a22; 593 | font-size: 1.4rem; 594 | } 595 | 596 | .footer-pages ul { 597 | list-style-type: none; 598 | } 599 | 600 | .footer-pages ul li { 601 | padding: 0.7rem 0; 602 | } 603 | 604 | .footer-pages-links { 605 | text-decoration: none; 606 | color: #274C5B; 607 | transition: all 0.1s; 608 | } 609 | 610 | .footer-pages-links:hover { 611 | color: #e32a22; 612 | font-weight: bold; 613 | } 614 | 615 | /* -------------------------- */ 616 | 617 | .footer-media { 618 | padding-top: 1.7rem; 619 | flex-basis: 30%; 620 | text-align: center; 621 | } 622 | 623 | .footer-img { 624 | max-width: 10rem; 625 | max-height: 4rem; 626 | text-align: center; 627 | margin-bottom: 1rem; 628 | } 629 | 630 | 631 | .footer-media-des { 632 | color: #274C5B; 633 | font-weight: bold; 634 | padding-bottom: 2rem; 635 | } 636 | 637 | .footer-media-medias { 638 | display: flex; 639 | justify-content: space-around; 640 | align-items: center; 641 | } 642 | 643 | .footer-media-media { 644 | /* padding: 1rem; */ 645 | width: 4.5rem; 646 | height: 4.5rem; 647 | background-color: #f9f8f8; 648 | border-radius: 50%; 649 | box-shadow: 2px 5px 4px 2px rgba(0, 0, 0, 0.2); 650 | text-align: center; 651 | position: relative; 652 | overflow: hidden; 653 | color: #fd4848; 654 | } 655 | 656 | .footer-media-media .fa { 657 | line-height: 4.5rem; 658 | font-size: 2rem; 659 | transition: all 0.5s; 660 | } 661 | 662 | .footer-media-media::before { 663 | content: ''; 664 | position: absolute; 665 | width: 100%; 666 | height: 100%; 667 | border-radius: 50%; 668 | background-color: #fd4848; 669 | top: -4rem; 670 | right: -3.5rem; 671 | } 672 | 673 | .footer-media-media:hover:before { 674 | animation: mtb 1s forwards; 675 | } 676 | 677 | .footer-media-media:hover .fa { 678 | color: #f9f8f8; 679 | transform: scale(1.1); 680 | } 681 | 682 | @keyframes mtb { 683 | 0% { 684 | top: -100px; 685 | right: -80px; 686 | } 687 | 688 | 70% { 689 | top: 20px; 690 | right: 20px; 691 | } 692 | 693 | 100% { 694 | top: 0; 695 | right: 0; 696 | } 697 | } 698 | 699 | /* ------------------------- */ 700 | 701 | .footer-contactus-sub { 702 | font-weight: bold; 703 | color: #274C5B; 704 | margin: 0; 705 | padding-bottom: 0.4rem; 706 | } 707 | 708 | .footer-contactus-des { 709 | color: #525c60; 710 | margin: 0; 711 | padding-bottom: 1.3rem; 712 | } 713 | 714 | 715 | /* ----------------------- */ 716 | 717 | .copyright { 718 | border-top: 0.15rem solid #525c603d; 719 | } 720 | 721 | .copyright-text { 722 | font-size: 1rem; 723 | color: #525c6080; 724 | text-align: center; 725 | margin: 0; 726 | padding: 0.7rem 0; 727 | } 728 | 729 | /* ------------------------------------------------------------------------------------------------------------------------------------------------ */ 730 | /* all recipe page */ 731 | 732 | #header-pic { 733 | /* background-image: url(/images/header-recipe.png); */ 734 | background-position: center; 735 | background-repeat: no-repeat; 736 | width: 100%; 737 | height: 19.375rem; 738 | text-align: center; 739 | } 740 | 741 | .header-pic-text { 742 | margin: 0; 743 | color: #274C5B; 744 | font-size: 4.25rem; 745 | padding-top: 4rem; 746 | padding-bottom: 3rem; 747 | } 748 | 749 | .search-box { 750 | display: inline-block; 751 | background-color: #fff; 752 | padding: 0.7rem 2rem; 753 | border-radius: 10rem; 754 | border: 0.1rem solid #96a2a794; 755 | box-shadow: 2px 5px 4px 2px rgba(0, 0, 0, 0.3); 756 | } 757 | 758 | .search-box input[type="text"] { 759 | padding: 0.5rem 1rem; 760 | width: 35rem; 761 | border: none; 762 | outline: none; 763 | font-weight: bold; 764 | } 765 | 766 | .search-box button { 767 | background-color: #fff; 768 | border: none; 769 | outline: none; 770 | cursor: pointer; 771 | font-size: 1.2rem; 772 | padding: 0.5rem 0.6rem; 773 | border-radius: 10rem; 774 | transition: 0.2s; 775 | } 776 | 777 | .search-box button:hover { 778 | background-color: #f3f3f3; 779 | } 780 | 781 | /* ------------------------------------------------------------------------------------------------------------------------------------------------ */ 782 | /* recipe page */ 783 | 784 | #recipe-header { 785 | position: relative; 786 | } 787 | 788 | .recipe-header-img { 789 | width: 100%; 790 | height: 400px; 791 | } 792 | 793 | .pRecipe-title { 794 | background-color: #fff; 795 | width: 70%; 796 | border-top-right-radius: 2.5rem; 797 | border-top-left-radius: 2.5rem; 798 | padding: 1.5rem 1rem; 799 | top: 8rem; 800 | right: 15%; 801 | position: absolute; 802 | } 803 | 804 | .back-link-img { 805 | width: 2rem; 806 | padding-right: 0.4rem; 807 | } 808 | 809 | .pRecipe-title .section-title { 810 | font-weight: 900; 811 | margin: 0; 812 | color: #274C5B; 813 | padding: 1rem 0; 814 | } 815 | 816 | .pRecipe-title .section-des { 817 | font-size: 1.1rem; 818 | line-height: 2rem; 819 | font-weight: bold; 820 | margin: 0; 821 | padding: 0; 822 | } 823 | 824 | .pRecipe { 825 | display: flex; 826 | flex-direction: column; 827 | align-items: center; 828 | } 829 | 830 | .pRecipe-top { 831 | margin-top: 2rem; 832 | display: flex; 833 | justify-content: space-between; 834 | align-items: center; 835 | margin-top: 5rem; 836 | width: 70%; 837 | } 838 | 839 | .pRecipe-img { 840 | max-width: 26rem; 841 | border-radius: 1rem; 842 | } 843 | 844 | .pRecipe-mavad ul li { 845 | font-size: 1.3rem; 846 | font-weight: bold; 847 | padding: 0.4rem 0; 848 | } 849 | 850 | .pRecipe-bottom .section-title { 851 | text-align: right; 852 | font-weight: bold; 853 | margin: 0; 854 | } 855 | 856 | .pRecipe-bottom { 857 | width: 70%; 858 | margin-top: 4rem; 859 | } 860 | 861 | .pRecipe-bottom ul li { 862 | font-size: 1.2rem; 863 | font-weight: 500; 864 | line-height: 2rem; 865 | padding: 0.6rem 0; 866 | } 867 | 868 | 869 | 870 | 871 | 872 | 873 | /* Project Responsive */ 874 | 875 | @media only screen and (max-width: 1200px) { 876 | .container { 877 | max-width: 960px; 878 | } 879 | } 880 | 881 | @media only screen and (max-width: 992px) { 882 | .container { 883 | max-width: 720px; 884 | } 885 | } 886 | 887 | @media only screen and (max-width: 768px) { 888 | .container { 889 | max-width: 540px; 890 | } 891 | } 892 | 893 | @media only screen and (max-width: 576px) { 894 | .container { 895 | max-width: 100%; 896 | } 897 | } 898 | 899 | .glow { 900 | top: -10%; 901 | left: -10%; 902 | width: 120%; 903 | height: 120%; 904 | border-radius: 100%; 905 | } 906 | 907 | .glow-1 { 908 | animation: glow1 4s linear infinite; 909 | } 910 | 911 | .glow-2 { 912 | animation: glow2 4s linear infinite; 913 | animation-delay: 100ms; 914 | } 915 | 916 | .glow-3 { 917 | animation: glow3 4s linear infinite; 918 | animation-delay: 200ms; 919 | } 920 | 921 | .glow-4 { 922 | animation: glow4 4s linear infinite; 923 | animation-delay: 300ms; 924 | } 925 | 926 | @keyframes glow1 { 927 | 0% { 928 | transform: translate(10%, 10%) scale(1); 929 | } 930 | 25% { 931 | transform: translate(-10%, 10%) scale(1); 932 | } 933 | 50% { 934 | transform: translate(-10%, -10%) scale(1); 935 | } 936 | 75% { 937 | transform: translate(10%, -10%) scale(1); 938 | } 939 | 100% { 940 | transform: translate(10%, 10%) scale(1); 941 | } 942 | } 943 | 944 | @keyframes glow2 { 945 | 0% { 946 | transform: translate(-10%, -10%) scale(1); 947 | } 948 | 25% { 949 | transform: translate(10%, -10%) scale(1); 950 | } 951 | 50% { 952 | transform: translate(10%, 10%) scale(1); 953 | } 954 | 75% { 955 | transform: translate(-10%, 10%) scale(1); 956 | } 957 | 100% { 958 | transform: translate(-10%, -10%) scale(1); 959 | } 960 | } 961 | 962 | @keyframes glow3 { 963 | 0% { 964 | transform: translate(-10%, 10%) scale(1); 965 | } 966 | 25% { 967 | transform: translate(-10%, -10%) scale(1); 968 | } 969 | 50% { 970 | transform: translate(10%, -10%) scale(1); 971 | } 972 | 75% { 973 | transform: translate(10%, 10%) scale(1); 974 | } 975 | 100% { 976 | transform: translate(-10%, 10%) scale(1); 977 | } 978 | } 979 | 980 | @keyframes glow4 { 981 | 0% { 982 | transform: translate(10%, -10%) scale(1); 983 | } 984 | 25% { 985 | transform: translate(10%, 10%) scale(1); 986 | } 987 | 50% { 988 | transform: translate(-10%, 10%) scale(1); 989 | } 990 | 75% { 991 | transform: translate(-10%, -10%) scale(1); 992 | } 993 | 100% { 994 | transform: translate(10%, -10%) scale(1); 995 | } 996 | } -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { library } from '@fortawesome/fontawesome-svg-core'; 2 | import { fab } from "@fortawesome/free-brands-svg-icons"; 3 | import { fas } from "@fortawesome/free-solid-svg-icons"; 4 | import Footer from "../components/Footer"; 5 | import Navbar from "../components/Header"; 6 | import vazirmatn from "../styles/fonts"; 7 | import "./globals.css"; 8 | 9 | library.add(fab, fas) 10 | 11 | // export const metadata: Metadata = { 12 | // title: "Recipe App", 13 | // description: "Welcome to Recpie app", 14 | // applicationName: "RecipeApp", 15 | // authors:[ 16 | // { 17 | // name: "Ehsan Ghaffar", 18 | // url: "https://ehsanghaffarii.ir" 19 | // } 20 | // ], 21 | // creator: "Ehsan Ghaffar", 22 | // verification: { 23 | // google: "aG69rfEfYwvFjNKS3C-jUj60PsqRr2LO9lHyKw0wNFE" 24 | // }, 25 | // openGraph: { 26 | // title: 'Nextjs Appdir Recipe App', 27 | // description: 'Recipe App created with next.js 13.4', 28 | // type: 'article', 29 | // publishedTime: '2023-01-01T00:00:00.000Z', 30 | // authors: ['Ehsan Ghaffar', 'Eindev'], 31 | // }, 32 | 33 | // }; 34 | 35 | 36 | export default function RootLayout({ 37 | children, 38 | }: { 39 | children: React.ReactNode; 40 | }) { 41 | return ( 42 | 43 | 47 | 48 | {children} 49 |