add: docker files + loading spinner & theme context
This commit is contained in:
parent
f1e8f62bb2
commit
7d88da6667
11
Dockerfile
Normal file
11
Dockerfile
Normal file
@ -0,0 +1,11 @@
|
||||
FROM node:18-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json ./
|
||||
|
||||
RUN yarn install
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD ["yarn", "start"]
|
11
docker-compose.yml
Normal file
11
docker-compose.yml
Normal file
@ -0,0 +1,11 @@
|
||||
services:
|
||||
parafiaborzeta:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: parafiaborzeta
|
||||
ports:
|
||||
- '3000:3000'
|
||||
volumes:
|
||||
- .:/app
|
||||
- /app/node_modules
|
@ -1,5 +0,0 @@
|
||||
export default function NavigationBar() {
|
||||
return <nav>
|
||||
|
||||
</nav>
|
||||
}
|
@ -1,23 +1,56 @@
|
||||
'use client'
|
||||
|
||||
import { Montserrat } from "next/font/google";
|
||||
import type { Metadata } from "next";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
import NavigationBar from "@/components/navigation_bar.component";
|
||||
import { ThemeProvider } from "@/providers/theme.provider";
|
||||
import { GlobalLayout, MainBlock } from "@/styles";
|
||||
import Loader from "@/components/loader.component";
|
||||
|
||||
const inter = Montserrat({ subsets: ["latin"] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Parafia pw. Niepokalanego Serca NMP w Borzęcie",
|
||||
description: "Parafia pw. Niepokalanego Serca NMP w Borzęcie",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const handleComplete = () => setLoading(false);
|
||||
|
||||
// simulate loading delay
|
||||
const timer = setTimeout(handleComplete, 1000);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
return <>
|
||||
<html lang="en">
|
||||
{/*
|
||||
can't use next.Metadata,
|
||||
because 'use client' + styled components are
|
||||
preventing it.
|
||||
|
||||
next/head wouldn't work too, so legacy solution is used
|
||||
*/}
|
||||
<head>
|
||||
<title>Parafia pw. Niepokalanego Serca NMP w Borzęcie</title>
|
||||
<meta name="description" content="Strona parafii pod wezwaniem Niepokalanego Serca Najświętszej Maryi Panny w Borzęcie." />
|
||||
</head>
|
||||
<body className={inter.className}>
|
||||
{children}
|
||||
<ThemeProvider>
|
||||
<GlobalLayout />
|
||||
{loading
|
||||
? <Loader />
|
||||
: <>
|
||||
<MainBlock>
|
||||
<NavigationBar />
|
||||
{children}
|
||||
</MainBlock>
|
||||
</>}
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
</>;
|
||||
}
|
@ -1,26 +1,17 @@
|
||||
'use client'
|
||||
|
||||
import { ThemeProvider } from "styled-components";
|
||||
|
||||
import { Button, GlobalLayout, MainBlock } from "./styles";
|
||||
import { Theme, darkTheme, lightTheme } from "./themes";
|
||||
import useLocalStorage from "./utils/local_storage";
|
||||
|
||||
const themes: Record<string, Theme> = {
|
||||
light: lightTheme,
|
||||
dark: darkTheme
|
||||
}
|
||||
import { useTheme } from "@/providers/theme.provider";
|
||||
import { Button } from "../styles";
|
||||
|
||||
export default function Home() {
|
||||
const [theme, setTheme] = useLocalStorage("theme", "light");
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
|
||||
return <ThemeProvider theme={themes[theme] ?? lightTheme}>
|
||||
<GlobalLayout />
|
||||
<MainBlock>
|
||||
<h1>Parafia w Borzęcie</h1>
|
||||
<p>Już wkrótce powstanie tutaj świeża strona parafii pod wezwaniem Niepokalanego Serca Maryi w Borzęcie.</p>
|
||||
<a href="tel:+48123456789">zadzwoń do proboszcza</a>
|
||||
<Button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>Przełącz motyw</Button>
|
||||
</MainBlock>
|
||||
</ThemeProvider>;
|
||||
return <>
|
||||
<h1>Parafia w Borzęcie</h1>
|
||||
<p>Już wkrótce powstanie tutaj świeża strona parafii pod wezwaniem Niepokalanego Serca Maryi w Borzęcie.</p>
|
||||
<a href="tel:+48123456789">zadzwoń do proboszcza</a>
|
||||
<hr />
|
||||
<h2>Aktualny motyw: {theme}</h2>
|
||||
<Button onClick={toggleTheme}>Przełącz motyw</Button>
|
||||
</>;
|
||||
}
|
@ -1,17 +1,18 @@
|
||||
export type ThemeMode = 'light' | 'dark'
|
||||
export interface Theme {
|
||||
background: string,
|
||||
padding: number,
|
||||
space_px: number,
|
||||
text: string
|
||||
}
|
||||
|
||||
export const darkTheme: Theme = {
|
||||
padding: 10,
|
||||
space_px: 10,
|
||||
background: "#2f3136",
|
||||
text: "#fff"
|
||||
}
|
||||
|
||||
export const lightTheme: Theme = {
|
||||
padding: 10,
|
||||
space_px: 10,
|
||||
background: "#eee",
|
||||
text: "#000"
|
||||
}
|
34
src/components/loader.component.tsx
Normal file
34
src/components/loader.component.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import styled, { keyframes } from "styled-components";
|
||||
|
||||
const spin = keyframes`
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
`;
|
||||
|
||||
const LoaderOverlay = styled.div`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
`;
|
||||
|
||||
const Spinner = styled.div`
|
||||
border: 8px solid ${({ theme }) => theme.text};
|
||||
border-top: 8px solid ${({ theme }) => theme.background};
|
||||
border-radius: 50%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
animation: ${spin} 1s linear infinite;
|
||||
`;
|
||||
|
||||
export default function Loader() {
|
||||
return <LoaderOverlay>
|
||||
<Spinner />
|
||||
</LoaderOverlay>
|
||||
};
|
33
src/components/navigation_bar.component.tsx
Normal file
33
src/components/navigation_bar.component.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import styled from "styled-components"
|
||||
import Link from "next/link"
|
||||
|
||||
export default function NavigationBar() {
|
||||
return <Navigation>
|
||||
<h1>Parafia w Borzęcie</h1>
|
||||
<NavigationLinks>
|
||||
<li><Link href="/">Strona główna</Link></li>
|
||||
<li><Link href="/contact">Kontakt</Link></li>
|
||||
</NavigationLinks>
|
||||
</Navigation>
|
||||
}
|
||||
|
||||
const Navigation = styled.nav`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
padding: ${({ theme }) => theme.space_px}px;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
`
|
||||
|
||||
const NavigationLinks = styled.ul`
|
||||
list-style-type: none;
|
||||
li {
|
||||
margin: ${({ theme }) => theme.space_px}px;
|
||||
display: block;
|
||||
float: left;
|
||||
}
|
||||
`
|
41
src/providers/theme.provider.tsx
Normal file
41
src/providers/theme.provider.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import { createContext, useContext, useMemo, PropsWithChildren } from "react";
|
||||
import { ThemeProvider as StyledThemeProvider } from "styled-components";
|
||||
|
||||
import { ThemeMode, lightTheme, darkTheme } from "@/app/themes";
|
||||
import useLocalStorage from "@/utils/local_storage";
|
||||
|
||||
const ThemeContext = createContext({
|
||||
theme: "light",
|
||||
toggleTheme: () => { },
|
||||
} as {
|
||||
theme: ThemeMode,
|
||||
toggleTheme: () => void
|
||||
});
|
||||
|
||||
export const ThemeProvider = ({ children }: PropsWithChildren) => {
|
||||
const [theme, setTheme] = useLocalStorage<ThemeMode>("theme", "light");
|
||||
|
||||
const toggleTheme = () => {
|
||||
setTheme(previous => previous === "light" ? "dark" : "light");
|
||||
};
|
||||
|
||||
const value = useMemo(() => ({ theme, toggleTheme }), [theme]);
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={value}>
|
||||
<StyledThemeProvider theme={theme === "light" ? lightTheme : darkTheme}>
|
||||
{children}
|
||||
</StyledThemeProvider>
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useTheme = () => {
|
||||
const context = useContext(ThemeContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useTheme must be used within a ThemeProvider");
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
@ -39,7 +39,8 @@ export const MainBlock = styled.main`
|
||||
export const Button = styled.button`
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: ${({ theme }) => theme.padding}px;
|
||||
cursor: pointer;
|
||||
padding: ${({ theme }) => theme.space_px}px;
|
||||
color: ${({ theme }) => theme.background};
|
||||
background-color: ${({ theme }) => theme.text};
|
||||
`;
|
@ -9,8 +9,9 @@ export default function useLocalStorage<T>(
|
||||
|
||||
useEffect(() => {
|
||||
const item = window.localStorage.getItem(key)
|
||||
|
||||
if (item) {
|
||||
setValue(JSON.parse(item))
|
||||
setValue(JSON.parse(item))
|
||||
}
|
||||
|
||||
return () => {
|
Loading…
Reference in New Issue
Block a user