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 { 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"] });
|
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({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: React.ReactNode;
|
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">
|
<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}>
|
<body className={inter.className}>
|
||||||
{children}
|
<ThemeProvider>
|
||||||
|
<GlobalLayout />
|
||||||
|
{loading
|
||||||
|
? <Loader />
|
||||||
|
: <>
|
||||||
|
<MainBlock>
|
||||||
|
<NavigationBar />
|
||||||
|
{children}
|
||||||
|
</MainBlock>
|
||||||
|
</>}
|
||||||
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
</>;
|
||||||
}
|
}
|
@ -1,26 +1,17 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { ThemeProvider } from "styled-components";
|
import { useTheme } from "@/providers/theme.provider";
|
||||||
|
import { Button } from "../styles";
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const [theme, setTheme] = useLocalStorage("theme", "light");
|
const { theme, toggleTheme } = useTheme();
|
||||||
|
|
||||||
return <ThemeProvider theme={themes[theme] ?? lightTheme}>
|
return <>
|
||||||
<GlobalLayout />
|
<h1>Parafia w Borzęcie</h1>
|
||||||
<MainBlock>
|
<p>Już wkrótce powstanie tutaj świeża strona parafii pod wezwaniem Niepokalanego Serca Maryi w Borzęcie.</p>
|
||||||
<h1>Parafia w Borzęcie</h1>
|
<a href="tel:+48123456789">zadzwoń do proboszcza</a>
|
||||||
<p>Już wkrótce powstanie tutaj świeża strona parafii pod wezwaniem Niepokalanego Serca Maryi w Borzęcie.</p>
|
<hr />
|
||||||
<a href="tel:+48123456789">zadzwoń do proboszcza</a>
|
<h2>Aktualny motyw: {theme}</h2>
|
||||||
<Button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>Przełącz motyw</Button>
|
<Button onClick={toggleTheme}>Przełącz motyw</Button>
|
||||||
</MainBlock>
|
</>;
|
||||||
</ThemeProvider>;
|
|
||||||
}
|
}
|
@ -1,17 +1,18 @@
|
|||||||
|
export type ThemeMode = 'light' | 'dark'
|
||||||
export interface Theme {
|
export interface Theme {
|
||||||
background: string,
|
background: string,
|
||||||
padding: number,
|
space_px: number,
|
||||||
text: string
|
text: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const darkTheme: Theme = {
|
export const darkTheme: Theme = {
|
||||||
padding: 10,
|
space_px: 10,
|
||||||
background: "#2f3136",
|
background: "#2f3136",
|
||||||
text: "#fff"
|
text: "#fff"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const lightTheme: Theme = {
|
export const lightTheme: Theme = {
|
||||||
padding: 10,
|
space_px: 10,
|
||||||
background: "#eee",
|
background: "#eee",
|
||||||
text: "#000"
|
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`
|
export const Button = styled.button`
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
padding: ${({ theme }) => theme.padding}px;
|
cursor: pointer;
|
||||||
|
padding: ${({ theme }) => theme.space_px}px;
|
||||||
color: ${({ theme }) => theme.background};
|
color: ${({ theme }) => theme.background};
|
||||||
background-color: ${({ theme }) => theme.text};
|
background-color: ${({ theme }) => theme.text};
|
||||||
`;
|
`;
|
@ -9,8 +9,9 @@ export default function useLocalStorage<T>(
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const item = window.localStorage.getItem(key)
|
const item = window.localStorage.getItem(key)
|
||||||
|
|
||||||
if (item) {
|
if (item) {
|
||||||
setValue(JSON.parse(item))
|
setValue(JSON.parse(item))
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
Loading…
Reference in New Issue
Block a user