Styling and structure of main recipes page.
This commit is contained in:
parent
159f0177eb
commit
a983d49f22
11
src/components/footer/Footer.tsx
Normal file
11
src/components/footer/Footer.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import './footer.module.css'
|
||||
|
||||
const Footer = () => {
|
||||
return (
|
||||
<footer>
|
||||
<p>Copyright 2024 Jesse R. Brault. All rights reserved.</p>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
export default Footer
|
3
src/components/footer/footer.module.css
Normal file
3
src/components/footer/footer.module.css
Normal file
@ -0,0 +1,3 @@
|
||||
footer {
|
||||
padding: 20px;
|
||||
}
|
27
src/components/header/Header.tsx
Normal file
27
src/components/header/Header.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { useNavigate, useRouter } from '@tanstack/react-router'
|
||||
import { useAuth } from '../../auth'
|
||||
import classes from './header.module.css'
|
||||
|
||||
const Header = () => {
|
||||
const auth = useAuth()
|
||||
const router = useRouter()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const onLogout = async () => {
|
||||
auth.clearToken(async () => {
|
||||
await router.invalidate()
|
||||
await navigate({ to: '/login' })
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<header>
|
||||
<h1 className={classes.mealsMadeEasy}>Meals Made Easy</h1>
|
||||
<nav>
|
||||
<button onClick={onLogout}>Logout</button>
|
||||
</nav>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
export default Header
|
11
src/components/header/header.module.css
Normal file
11
src/components/header/header.module.css
Normal file
@ -0,0 +1,11 @@
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: var(--primary-red);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.meals-made-easy {
|
||||
color: var(--primary-white);
|
||||
}
|
@ -38,28 +38,40 @@ const RecipeCard = ({
|
||||
/>
|
||||
</Link>
|
||||
<div className={classes.infoContainer}>
|
||||
<Link
|
||||
to="/recipes/$username/$slug"
|
||||
params={{
|
||||
username: ownerUsername,
|
||||
slug
|
||||
}}
|
||||
>
|
||||
<h1 className={classes.title}>{title}</h1>
|
||||
</Link>
|
||||
<span>
|
||||
<FontAwesomeIcon icon="star" size="sm" />
|
||||
{starCount}
|
||||
</span>
|
||||
<span>
|
||||
<FontAwesomeIcon icon="user" />
|
||||
{ownerUsername}
|
||||
</span>
|
||||
{isPublic ? (
|
||||
<FontAwesomeIcon icon="globe" size="sm" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon="lock" size="sm" />
|
||||
)}
|
||||
<div className={classes.infoRow}>
|
||||
<Link
|
||||
className={classes.titleLink}
|
||||
to="/recipes/$username/$slug"
|
||||
params={{
|
||||
username: ownerUsername,
|
||||
slug
|
||||
}}
|
||||
>
|
||||
<h1 className={classes.title}>{title}</h1>
|
||||
</Link>
|
||||
<span className={classes.starInfo}>
|
||||
<FontAwesomeIcon
|
||||
icon="star"
|
||||
className={classes.star}
|
||||
size="sm"
|
||||
/>
|
||||
{starCount}
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.infoRow}>
|
||||
<span className={classes.userInfo}>
|
||||
<FontAwesomeIcon
|
||||
icon="user"
|
||||
className={classes.userIcon}
|
||||
/>
|
||||
{ownerUsername}
|
||||
</span>
|
||||
{isPublic ? (
|
||||
<FontAwesomeIcon icon="globe" size="sm" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon="lock" size="sm" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
|
@ -1,18 +1,44 @@
|
||||
.recipe-card {
|
||||
max-width: 400px;
|
||||
border: 1px solid black;
|
||||
justify-self: stretch;
|
||||
}
|
||||
|
||||
.recipe-image {
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
max-height: 200px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.info-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
align-items: baseline;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 10px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-block: 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.star-info {
|
||||
display: flex;
|
||||
column-gap: 5px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.star {
|
||||
color: var(--primary-yellow);
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
column-gap: 5px;
|
||||
}
|
||||
|
||||
.user-icon {
|
||||
color: var(--primary-red);
|
||||
}
|
||||
|
24
src/main.css
Normal file
24
src/main.css
Normal file
@ -0,0 +1,24 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap');
|
||||
|
||||
:root {
|
||||
--primary-white: #ffffff;
|
||||
--primary-red: #91351d;
|
||||
--primary-yellow: #ffb61d;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--primary-red);
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: var(--primary-red);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: hsl(from var(--primary-red) h s l / 0.9);
|
||||
}
|
@ -6,6 +6,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { AuthProvider, useAuth } from './auth'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { fas } from '@fortawesome/free-solid-svg-icons'
|
||||
import './main.css'
|
||||
|
||||
// Font-Awesome: load icons
|
||||
library.add(fas)
|
||||
|
@ -4,6 +4,7 @@ import { useState } from 'react'
|
||||
import { useAuth } from '../../auth'
|
||||
import { ApiError } from '../../api/ApiError'
|
||||
import RecipeCard from '../../components/recipe-card/RecipeCard'
|
||||
import classes from './recipes.module.css'
|
||||
|
||||
const Recipes = () => {
|
||||
const [pageNumber, setPageNumber] = useState(0)
|
||||
@ -35,20 +36,29 @@ const Recipes = () => {
|
||||
return <p>Error: {error.message}</p>
|
||||
}
|
||||
} else {
|
||||
return data.content.map(view => (
|
||||
<RecipeCard
|
||||
key={view.id}
|
||||
title={view.title}
|
||||
ownerUsername={view.ownerUsername}
|
||||
slug={view.slug}
|
||||
mainImageUrl={view.mainImage.url}
|
||||
mainImageAlt={
|
||||
view.mainImage.alt ? view.mainImage.alt : undefined
|
||||
}
|
||||
starCount={view.starCount}
|
||||
isPublic={view.isPublic}
|
||||
/>
|
||||
))
|
||||
return (
|
||||
<>
|
||||
<h1>Recipes</h1>
|
||||
<section className={classes.recipeList}>
|
||||
{data.content.map(view => (
|
||||
<RecipeCard
|
||||
key={view.id}
|
||||
title={view.title}
|
||||
ownerUsername={view.ownerUsername}
|
||||
slug={view.slug}
|
||||
mainImageUrl={view.mainImage.url}
|
||||
mainImageAlt={
|
||||
view.mainImage.alt
|
||||
? view.mainImage.alt
|
||||
: undefined
|
||||
}
|
||||
starCount={view.starCount}
|
||||
isPublic={view.isPublic}
|
||||
/>
|
||||
))}
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
23
src/pages/recipes/recipes.module.css
Normal file
23
src/pages/recipes/recipes.module.css
Normal file
@ -0,0 +1,23 @@
|
||||
.recipe-list {
|
||||
display: grid;
|
||||
justify-content: space-evenly;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 900px) {
|
||||
.recipe-list {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1300px) {
|
||||
.recipe-list {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1700px) {
|
||||
.recipe-list {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
3
src/routes/__root.module.css
Normal file
3
src/routes/__root.module.css
Normal file
@ -0,0 +1,3 @@
|
||||
main {
|
||||
padding: 20px;
|
||||
}
|
@ -1,39 +1,18 @@
|
||||
import {
|
||||
Outlet,
|
||||
createRootRouteWithContext,
|
||||
useNavigate,
|
||||
useRouter
|
||||
} from '@tanstack/react-router'
|
||||
import { Outlet, createRootRouteWithContext } from '@tanstack/react-router'
|
||||
import { TanStackRouterDevtools } from '@tanstack/router-devtools'
|
||||
import RouterContext from '../RouterContext'
|
||||
import { useAuth } from '../auth'
|
||||
import Header from '../components/header/Header'
|
||||
import Footer from '../components/footer/Footer'
|
||||
import './__root.module.css'
|
||||
|
||||
const RootLayout = () => {
|
||||
const auth = useAuth()
|
||||
const router = useRouter()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const onLogout = async () => {
|
||||
auth.clearToken(async () => {
|
||||
await router.invalidate()
|
||||
await navigate({ to: '/login' })
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<header>
|
||||
<h1>Meals Made Easy</h1>
|
||||
<nav>
|
||||
<button onClick={onLogout}>Logout</button>
|
||||
</nav>
|
||||
</header>
|
||||
<Header />
|
||||
<main>
|
||||
<Outlet />
|
||||
</main>
|
||||
<footer>
|
||||
<p>Copyright 2024 Jesse R. Brault. All rights reserved.</p>
|
||||
</footer>
|
||||
<Footer />
|
||||
<TanStackRouterDevtools position="bottom-right" />
|
||||
</>
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user