├── .eslintrc.json
├── .gitignore
├── README.md
├── components
├── common
│ ├── PageHead.tsx
│ ├── PageHeading.tsx
│ └── PageSection.tsx
├── footers
│ └── RootFooter.tsx
├── headers
│ └── RootHeader.tsx
├── interactive
│ ├── ColorSwitcher.tsx
│ └── LanguageSwitcher.tsx
├── layouts
│ ├── DashboardLayout.tsx
│ └── RootLayout.tsx
├── navigations
│ ├── DashboardNavigation.tsx
│ └── RootNavigation.tsx
└── ui
│ ├── Popover.tsx
│ └── Tooltip.tsx
├── helpers
├── globalStyles.ts
├── initPocketbase.ts
├── isOnline.ts
└── theme.ts
├── hooks
└── useSaveLocaleCookie.ts
├── i18n.json
├── locales
├── de
│ └── common.json
└── en
│ └── common.json
├── next.config.js
├── package-lock.json
├── package.json
├── pages
├── _app.tsx
├── _document.tsx
├── dashboard
│ ├── index.tsx
│ └── settings
│ │ └── index.tsx
├── index.tsx
├── login
│ └── index.tsx
└── user
│ ├── [slug]
│ ├── edit
│ │ └── index.tsx
│ └── index.tsx
│ ├── index.tsx
│ └── me
│ ├── edit
│ └── index.tsx
│ └── index.tsx
├── postcss.config.js
├── public
└── favicon.ico
├── styles
└── globals.css
├── tailwind.config.js
├── tsconfig.json
└── types
└── user.ts
/.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 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
38 | # VS Code
39 | /.vscode/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Next.js without app directory starter kit
2 |
3 | This is my boilerplate for the stable Next.js version without app directory. It is mainly powered by Pocketbase.
4 |
5 | ## Main Features
6 |
7 | - Next.js for frontend
8 | - Pocketbase for backend
9 | - Pocketbase for database
10 | - Pocketbase for authentication
11 | - Pocketbase for image hosting
12 |
13 | ## Other features
14 |
15 | - NextUI
16 | - TailwindCSS
17 | - NextUI
18 | - React-Icons
19 | - Dark-Mode with NextUI and next-themes
20 | - Internationalization with next-translate
21 | - Formik and Yup for easy and hasslefree forms and form validation (Coming Soon)
22 | - Custom authentication flow with Pocketbase
23 | - Custom basic user system (WIP)
24 |
--------------------------------------------------------------------------------
/components/common/PageHead.tsx:
--------------------------------------------------------------------------------
1 | import Head from "next/head";
2 | import useTranslation from "next-translate/useTranslation";
3 |
4 | export default function PageHead({ title }: { title: string }) {
5 | const { t } = useTranslation("common");
6 |
7 | return (
8 |
9 | {t("sitename") + " | " + title}
10 |
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/components/common/PageHeading.tsx:
--------------------------------------------------------------------------------
1 | //Import Components
2 | import { Container, Text } from "@nextui-org/react";
3 |
4 | export default function PageHeading({ text }: { text: string }) {
5 | return (
6 |
7 | {text}
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/components/common/PageSection.tsx:
--------------------------------------------------------------------------------
1 | // Import components
2 | import { Grid, Row, Text } from "@nextui-org/react";
3 |
4 | type Props = {
5 | title: string;
6 | children: React.ReactNode;
7 | };
8 |
9 | export default function PageSection({ title, children }: Props) {
10 | return (
11 |
12 |
13 | {title}
14 |
15 | {children}
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/components/footers/RootFooter.tsx:
--------------------------------------------------------------------------------
1 | import { Container } from "@nextui-org/react";
2 |
3 | export default function RootFooter() {
4 | return (
5 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/components/headers/RootHeader.tsx:
--------------------------------------------------------------------------------
1 | import RootNavigation from "../navigations/RootNavigation";
2 |
3 | export default function RootHeader() {
4 | return (
5 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/components/interactive/ColorSwitcher.tsx:
--------------------------------------------------------------------------------
1 | // Imoport components
2 | import { useTheme as useNextTheme } from "next-themes";
3 | import { Switch, useTheme } from "@nextui-org/react";
4 |
5 | // Import icons
6 | import { IoMoon, IoSunny } from "react-icons/io5";
7 | import { useEffect, useState } from "react";
8 |
9 | // Fire the component
10 | export default function ColorSwitcher({
11 | size,
12 | shadow,
13 | }: {
14 | size?: "xs" | "sm" | "md" | "lg" | "xl";
15 | shadow?: boolean;
16 | }) {
17 | const { setTheme } = useNextTheme();
18 | const { isDark } = useTheme();
19 |
20 | // Set loading state
21 | const [isLoading, setIsLoading] = useState(true);
22 |
23 | // Set loading state to false when component is mounted
24 | useEffect(() => {
25 | setIsLoading(false);
26 | }, []);
27 |
28 | // Return empty switch if loading
29 | if (isLoading) return ;
30 |
31 | // Return switch
32 | return (
33 | }
40 | iconOff={}
41 | onChange={(e) => setTheme(e.target.checked ? "dark" : "light")}
42 | />
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/components/interactive/LanguageSwitcher.tsx:
--------------------------------------------------------------------------------
1 | // Import components
2 | import useTranslation from "next-translate/useTranslation";
3 | import setLanguage from "next-translate/setLanguage";
4 | import { setCookie } from "cookies-next";
5 | import { Button } from "@nextui-org/react";
6 |
7 | // Import icons
8 | import { IoLanguage } from "react-icons/io5";
9 |
10 | // Import i18n config and set locales
11 | import i18nConfig from "../../i18n.json";
12 | const { locales } = i18nConfig;
13 |
14 | // Fire the component
15 | export default function ChangeLanguage() {
16 | const { t, lang } = useTranslation("common");
17 |
18 | return (
19 | // Map though locales and return a button for each
20 | <>
21 | {locales.map((lng) => {
22 | if (lng === lang) return null;
23 |
24 | return (
25 | }
32 | onClick={async () => {
33 | setCookie("NEXT_LOCALE", lng, {
34 | maxAge: 365 * 24 * 60 * 60,
35 | });
36 | await setLanguage(lng);
37 | }}
38 | >
39 | {t(lng)}
40 |
41 | );
42 | })}
43 | >
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/components/layouts/DashboardLayout.tsx:
--------------------------------------------------------------------------------
1 | // Import components
2 | import { Container } from "@nextui-org/react";
3 | import DashboardNavigation from "../navigations/DashboardNavigation";
4 |
5 | // Import types
6 | import type { authData } from "../../types/user";
7 |
8 | type Props = {
9 | children: React.ReactNode;
10 | authData: {
11 | authData: authData;
12 | isLoggedIn: boolean;
13 | roles: string[];
14 | };
15 | };
16 |
17 | // Fire up the layout
18 | export default function DashboardLayout({ children, authData }: Props) {
19 | return (
20 |
21 |
28 | {children}
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/components/layouts/RootLayout.tsx:
--------------------------------------------------------------------------------
1 | // Import components
2 | import RootFooter from "../footers/RootFooter";
3 | import RootHeader from "../headers/RootHeader";
4 |
5 | // Fire up the layout
6 | export default function RootLayout({
7 | children,
8 | }: {
9 | children: React.ReactNode;
10 | }) {
11 | return (
12 |
13 |
14 | {children}
15 |
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/components/navigations/DashboardNavigation.tsx:
--------------------------------------------------------------------------------
1 | // Import components
2 | import Link from "next/link";
3 | import { useRouter } from "next/router";
4 | import { Avatar, Badge, Card, Collapse, Text } from "@nextui-org/react";
5 | import Tooltip from "../ui/Tooltip";
6 |
7 | // Import icons
8 | import { IoMdSettings } from "react-icons/io";
9 | import { IoSettingsSharp } from "react-icons/io5";
10 | import {
11 | FaChevronLeft,
12 | FaUsers,
13 | FaUsersSlash,
14 | FaUserEdit,
15 | } from "react-icons/fa";
16 | import { MdSpaceDashboard } from "react-icons/md";
17 | import { BiUserPin } from "react-icons/bi";
18 |
19 | // Import types
20 | import type { authData } from "../../types/user";
21 |
22 | type Props = {
23 | authData: authData;
24 | isLoggedIn: boolean;
25 | roles: string[];
26 | };
27 |
28 | // Items for main navigation
29 | const mainItems = [
30 | { name: "Dashboard", href: "/dashboard", icon: },
31 | { name: "Settings", href: "/dashboard/settings", icon: },
32 | { name: "Show Profile", href: "/user/me", icon: },
33 | { name: "Edit Profile", href: "/user/me/edt", icon: },
34 | ];
35 |
36 | // Items for user navigation
37 | const userItems = [
38 | { name: "User List", href: "/user", icon: },
39 | { name: "Banned Users", href: "/user", icon: },
40 | ];
41 |
42 | // Fire up the navigation
43 | export default function DashboardNavigation({
44 | authData,
45 | isLoggedIn,
46 | roles,
47 | }: Props) {
48 | const router = useRouter();
49 |
50 | return (
51 |
52 |
59 | Settings
60 |
61 | }
62 | subtitle={
63 |
64 | Manage your settings
65 |
66 | }
67 | expanded
68 | arrowIcon={}
69 | contentLeft={
70 |
96 | }
97 | >
98 |
126 |
127 |
128 | {isLoggedIn && roles && roles.some((item) => ["Superadmin", "Admin"]) && (
129 |
136 | User Management
137 |
144 |
149 | <>Admin>
150 |
151 |
152 |
153 | }
154 | subtitle={
155 |
156 | Manage user and permissions
157 |
158 | }
159 | arrowIcon={}
160 | >
161 |
189 |
190 | )}
191 |
192 | );
193 | }
194 |
--------------------------------------------------------------------------------
/components/navigations/RootNavigation.tsx:
--------------------------------------------------------------------------------
1 | // Import components
2 | import { Navbar, Text, Badge } from "@nextui-org/react";
3 | import useTranslation from "next-translate/useTranslation";
4 | import Link from "next/link";
5 | import { useRouter } from "next/router";
6 | import { setCookie } from "cookies-next";
7 | import ColorSwitcher from "../interactive/ColorSwitcher";
8 |
9 | const collapseItems = ["Features", "Customers", "Pricing"];
10 |
11 | export default function RootNavigation() {
12 | const { t } = useTranslation("common");
13 | const router = useRouter();
14 | const shortUrl = router.asPath.split("/")[1];
15 |
16 | return (
17 | <>
18 |
24 |
25 |
26 |
27 | {t("sitename")}
28 |
29 |
30 | ALPHA
31 |
32 |
33 |
38 |
39 |
40 | Home
41 |
42 |
43 |
44 |
45 | User
46 |
47 |
48 |
49 |
50 | Dashboard
51 |
52 |
53 |
54 | {
57 | setCookie("redirect_after_login", router.asPath);
58 | }}
59 | >
60 | Login
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | {collapseItems.map((item) => (
69 |
70 |
71 | {item}
72 |
73 |
74 | ))}
75 |
76 |
77 | >
78 | );
79 | }
80 |
--------------------------------------------------------------------------------
/components/ui/Popover.tsx:
--------------------------------------------------------------------------------
1 | // Import Components
2 | import { Button, Grid, Popover as UIPopover } from "@nextui-org/react";
3 |
4 | // Import types
5 | import { authData } from "../../types/user";
6 |
7 | type Props = {
8 | icon?: React.ReactNode;
9 | children: React.ReactNode;
10 | text: string;
11 | authData?: authData;
12 | isOpen: boolean;
13 | setIsOpen: (isOpen: boolean) => void;
14 | width?: string;
15 | offset?: number;
16 | padding?: string;
17 | };
18 |
19 | export default function Popover({
20 | children,
21 | icon,
22 | text,
23 | isOpen,
24 | setIsOpen,
25 | width,
26 | offset,
27 | padding,
28 | }: Props) {
29 | return (
30 | setIsOpen(!isOpen)}
34 | >
35 |
36 |
39 |
40 |
41 |
47 | {children}
48 |
49 |
50 |
51 | );
52 | }
53 |
--------------------------------------------------------------------------------
/components/ui/Tooltip.tsx:
--------------------------------------------------------------------------------
1 | // Import components
2 | import { Tooltip as UITooltip } from "@nextui-org/react";
3 |
4 | // Import types
5 | import type { ReactElement } from "react";
6 |
7 | type Props = {
8 | children: ReactElement;
9 | isAlternative?: boolean;
10 | text: string;
11 | alternativeText?: string;
12 | offset?: number;
13 | leaveDelay?: number;
14 | showArrow?: boolean;
15 | color?:
16 | | "default"
17 | | "primary"
18 | | "secondary"
19 | | "success"
20 | | "warning"
21 | | "error"
22 | | "invert";
23 | textColor?:
24 | | "default"
25 | | "primary"
26 | | "secondary"
27 | | "success"
28 | | "warning"
29 | | "error";
30 | click?: boolean;
31 | };
32 |
33 | // Render the tooltip
34 | export default function Tooltip({
35 | children,
36 | isAlternative,
37 | text,
38 | alternativeText,
39 | offset,
40 | leaveDelay,
41 | showArrow,
42 | color,
43 | textColor,
44 | click,
45 | }: Props) {
46 | return (
47 |
56 | {children}
57 |
58 | );
59 | }
60 |
--------------------------------------------------------------------------------
/helpers/globalStyles.ts:
--------------------------------------------------------------------------------
1 | const globalStyles = {
2 | body: {
3 | fontFamily: "Bai Jamjuree, sans-serif",
4 | },
5 | p: {
6 | fontFamily: "Bai Jamjuree, sans-serif",
7 | },
8 | h1: {
9 | fontFamily: "Chakra Petch, sans-serif",
10 | },
11 | h2: {
12 | fontFamily: "Chakra Petch, sans-serif",
13 | },
14 | h3: {
15 | fontFamily: "Chakra Petch, sans-serif",
16 | },
17 | h4: {
18 | fontFamily: "Chakra Petch, sans-serif",
19 | },
20 | h5: {
21 | fontFamily: "Chakra Petch, sans-serif",
22 | },
23 | h6: {
24 | fontFamily: "Chakra Petch, sans-serif",
25 | },
26 | };
27 |
28 | export default globalStyles;
29 |
--------------------------------------------------------------------------------
/helpers/initPocketbase.ts:
--------------------------------------------------------------------------------
1 | // Import components
2 | import PocketBase from "pocketbase";
3 |
4 | // Import types
5 | import type { GetServerSidePropsContext } from "next";
6 |
7 | export default async function initPocketBase(
8 | context: GetServerSidePropsContext
9 | ) {
10 | const pb = new PocketBase(process.env.NEXT_PUBLIC_API_URL);
11 |
12 | // Load the store data from cookie
13 | pb.authStore.loadFromCookie(context.req?.headers?.cookie || "");
14 |
15 | // Send new cookie if actual data
16 | pb.authStore.onChange(() => {
17 | context.res?.setHeader("set-cookie", pb.authStore.exportToCookie());
18 | });
19 |
20 | try {
21 | // Get the user data from the server if the auth is valid
22 | pb.authStore.isValid && (await pb.collection("users").authRefresh());
23 | } catch (_) {
24 | // Clear the auth store if the auth is invalid
25 | pb.authStore.clear();
26 | }
27 |
28 | // Update the last active date
29 | if (pb.authStore.isValid && pb.authStore.model) {
30 | await pb.collection("users").update(pb.authStore.model.id, {
31 | lastActive: new Date(),
32 | });
33 | }
34 |
35 | return pb;
36 | }
37 |
--------------------------------------------------------------------------------
/helpers/isOnline.ts:
--------------------------------------------------------------------------------
1 | // Importing types
2 | import type { authData } from "../types/user";
3 |
4 | //Fire the function
5 | export default function isOnline({ authData }: { authData: authData }) {
6 | // Return false if the user was not active in the last 5 minutes
7 | const lastActive = Math.floor(new Date(authData.lastActive).getTime() / 1000);
8 | const fiveMinutesAgo = Math.floor(Date.now() / 1000) - 60 * 5;
9 | if (fiveMinutesAgo - lastActive > 301) return false;
10 | // Return true if the user is online
11 | return true;
12 | }
13 |
--------------------------------------------------------------------------------
/helpers/theme.ts:
--------------------------------------------------------------------------------
1 | export const themeLight = {
2 | type: "light",
3 | theme: {
4 | fonts: {
5 | sans: "Bai Jamjuree, sans-serif",
6 | primary: "Bai Jamjuree, sans-serif",
7 | secondary: "Charaka Petch, sans-serif",
8 | },
9 | },
10 | };
11 |
12 | export const themeDark = {
13 | type: "dark",
14 | theme: {
15 | fonts: {
16 | sans: "Bai Jamjuree, sans-serif",
17 | primary: "Bai Jamjuree, sans-serif",
18 | secondary: "Charaka Petch, sans-serif",
19 | },
20 | },
21 | };
22 |
--------------------------------------------------------------------------------
/hooks/useSaveLocaleCookie.ts:
--------------------------------------------------------------------------------
1 | // Import components
2 | import { useRouter } from "next/router";
3 | import { useEffect } from "react";
4 | import { getCookie } from "cookies-next";
5 |
6 | // Fire the hook
7 | export default function useSaveLocaleCookie() {
8 | const { locale, defaultLocale } = useRouter();
9 | const language = getCookie("NEXT_LOCALE");
10 |
11 | // Save the locale cookie when the locale changes
12 | useEffect(saveLocaleCookie, [locale, defaultLocale, language]);
13 |
14 | // Save the locale cookie
15 | function saveLocaleCookie() {
16 | if (!language) {
17 | const date = new Date();
18 | const expire = 30 * 24 * 60 * 60 * 1000; // 30 days
19 | date.setTime(date.getTime() + expire);
20 | document.cookie = `NEXT_LOCALE=${locale};expires=${date.toUTCString()};path=/`;
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/i18n.json:
--------------------------------------------------------------------------------
1 | {
2 | "locales": ["en", "de"],
3 | "defaultLocale": "en",
4 | "pages": {
5 | "*": ["common"]
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/locales/de/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "sitename": "Development Vorlage",
3 | "meta_description": "Next.js + NextUI + Tailwind + Pocketbase = Vorlage",
4 | "title": "Next.js + NextUI + Tailwind + Pocketbase = Vorlage",
5 | "de": "German",
6 | "en": "English",
7 | "settings": "Einstellungen"
8 | }
9 |
--------------------------------------------------------------------------------
/locales/en/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "sitename": "Development Boilerplate",
3 | "meta_description": "Next.js + NextUI + Tailwind + Pocketbase = Boilerplate",
4 | "title": "Next.js + NextUI + Tailwind + Pocketbase = Boilerplate",
5 | "de": "Deutsch",
6 | "en": "English",
7 | "settings": "Settings"
8 | }
9 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | };
5 |
6 | const nextTranslate = require("next-translate");
7 |
8 | module.exports = nextConfig;
9 | module.exports = nextTranslate();
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nextjs-with-pocketbase",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@fontsource/bai-jamjuree": "^4.5.9",
13 | "@fontsource/chakra-petch": "^4.5.9",
14 | "@next/font": "13.1.0",
15 | "@nextui-org/react": "^1.0.0-beta.11",
16 | "@types/node": "18.11.17",
17 | "@types/react": "18.0.26",
18 | "@types/react-dom": "18.0.10",
19 | "cookies-next": "^2.1.1",
20 | "eslint": "8.30.0",
21 | "eslint-config-next": "13.1.0",
22 | "next": "13.1.0",
23 | "next-themes": "^0.2.1",
24 | "next-translate": "^1.6.0",
25 | "pocketbase": "^0.9.0",
26 | "react": "18.2.0",
27 | "react-dom": "18.2.0",
28 | "react-icons": "^4.7.1",
29 | "typescript": "4.9.4"
30 | },
31 | "devDependencies": {
32 | "autoprefixer": "^10.4.13",
33 | "postcss": "^8.4.20",
34 | "tailwindcss": "^3.2.4"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | // Import used fonts
2 | import "@fontsource/bai-jamjuree/400.css";
3 | import "@fontsource/bai-jamjuree/400-italic.css";
4 | import "@fontsource/bai-jamjuree/600.css";
5 | import "@fontsource/bai-jamjuree/700.css";
6 | import "@fontsource/bai-jamjuree/700-italic.css";
7 | import "@fontsource/chakra-petch/700.css";
8 | import "@fontsource/chakra-petch/700-italic.css";
9 |
10 | // Import global css
11 | import "../styles/globals.css";
12 |
13 | // Import components
14 | import Head from "next/head";
15 | import { NextUIProvider, createTheme, globalCss } from "@nextui-org/react";
16 | import { ThemeProvider as NextThemesProvider } from "next-themes";
17 | import globalStyles from "../helpers/globalStyles";
18 | import { themeLight, themeDark } from "../helpers/theme";
19 | import useSaveLocaleCookie from "../hooks/useSaveLocaleCookie";
20 |
21 | // Import and set types
22 | import type { ReactElement, ReactNode } from "react";
23 | import type { AppProps } from "next/app";
24 | import type { NextPage } from "next";
25 |
26 | export type NextPageWithLayout = NextPage
& {
27 | getLayout?: (page: ReactElement) => ReactNode;
28 | };
29 |
30 | type AppPropsWithLayout = AppProps & {
31 | Component: NextPageWithLayout;
32 | };
33 |
34 | // Create custom themes
35 | const lightTheme = createTheme(themeLight);
36 | const darkTheme = createTheme(themeDark);
37 |
38 | // Create global css
39 | const globalStyle = globalCss(globalStyles);
40 |
41 | // Fire the app
42 | export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
43 | const getLayout = Component.getLayout ?? ((page) => page);
44 |
45 | // Set global css
46 | globalStyle();
47 | // Save locale cookie
48 | useSaveLocaleCookie();
49 |
50 | return (
51 | <>
52 |
53 |
54 |
55 |
56 |
64 |
65 | {getLayout()}
66 |
67 |
68 | >
69 | );
70 | }
71 |
--------------------------------------------------------------------------------
/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | // Import components
2 | import React from "react";
3 | import Document, { Html, Head, Main, NextScript } from "next/document";
4 | import { CssBaseline } from "@nextui-org/react";
5 |
6 | // Fire the document
7 | class MyDocument extends Document {
8 | static async getInitialProps(ctx: any) {
9 | const initialProps = await Document.getInitialProps(ctx);
10 | return {
11 | ...initialProps,
12 | styles: React.Children.toArray([initialProps.styles]),
13 | };
14 | }
15 |
16 | render() {
17 | return (
18 |
19 | {CssBaseline.flush()}
20 |
21 |
22 |
23 |
24 |
25 | );
26 | }
27 | }
28 |
29 | export default MyDocument;
30 |
--------------------------------------------------------------------------------
/pages/dashboard/index.tsx:
--------------------------------------------------------------------------------
1 | //Import components
2 | import Head from "next/head";
3 | import { Container } from "@nextui-org/react";
4 | import useTranslation from "next-translate/useTranslation";
5 | import { getCookie } from "cookies-next";
6 | import Layout from "../../components/layouts/RootLayout";
7 | import initPocketBase from "../../helpers/initPocketbase";
8 | import DashboardLayout from "../../components/layouts/DashboardLayout";
9 | import PageHeading from "../../components/common/PageHeading";
10 |
11 | // Import types
12 | import type { ReactElement } from "react";
13 | import type { GetServerSidePropsContext } from "next";
14 | import type { authData } from "../../types/user";
15 |
16 | // Fire the Home page
17 | export default function Dashboard({
18 | isLoggedIn,
19 | authData,
20 | roles,
21 | }: {
22 | isLoggedIn: boolean;
23 | authData: authData;
24 | roles: string[];
25 | }) {
26 | const { t } = useTranslation("common");
27 |
28 | return (
29 | <>
30 |
31 | {t("sitename") + " | Dashboard"}
32 |
33 |
34 |
35 | Content
36 | >
37 | );
38 | }
39 |
40 | // Export serverside props
41 | export async function getServerSideProps(context: GetServerSidePropsContext) {
42 | // Get the language cookie
43 | const langCookie = getCookie("NEXT_LOCALE", context.res);
44 |
45 | // Init PocketBase
46 | const pb = await initPocketBase(context);
47 |
48 | // Strip the authData from the pb authStore
49 | const authData = await JSON.parse(JSON.stringify(pb.authStore));
50 |
51 | let userRoles;
52 | let roles;
53 |
54 | // If the user is not logged in, redirect to the login page
55 | if (!pb.authStore.isValid) {
56 | return {
57 | redirect: {
58 | destination: "/login",
59 | permanent: false,
60 | },
61 | };
62 | }
63 |
64 | // If the language cookie is not the actual language, redirect to correct language
65 | if (langCookie && langCookie !== context.locale) {
66 | return {
67 | redirect: {
68 | destination: "/" + langCookie + context.resolvedUrl,
69 | permanent: false,
70 | },
71 | };
72 | }
73 |
74 | // Get the user roles
75 | await pb
76 | .collection("users")
77 | .getOne(authData.baseModel.id, {
78 | expand: "roles",
79 | })
80 | .then((res) => {
81 | userRoles = res.expand.roles;
82 | roles = userRoles.map((role: { name: string }) => role.name);
83 | })
84 | .catch(() => {
85 | roles = [];
86 | });
87 |
88 | // Return the props if user is logged in and correct language is set
89 | return {
90 | props: {
91 | isLoggedIn: pb.authStore.isValid,
92 | authData: authData.baseModel,
93 | roles: roles,
94 | },
95 | };
96 | }
97 |
98 | Dashboard.getLayout = function getLayout(page: ReactElement) {
99 | return (
100 |
101 | {page}
102 |
103 | );
104 | };
105 |
--------------------------------------------------------------------------------
/pages/dashboard/settings/index.tsx:
--------------------------------------------------------------------------------
1 | //Import components
2 | import { useState } from "react";
3 | import { useRouter } from "next/router";
4 | import PocketBase from "pocketbase";
5 | import {
6 | Container,
7 | Text,
8 | Row,
9 | Switch,
10 | Grid,
11 | Checkbox,
12 | Button,
13 | } from "@nextui-org/react";
14 | import useTranslation from "next-translate/useTranslation";
15 | import setLanguage from "next-translate/setLanguage";
16 | import { deleteCookie, getCookie } from "cookies-next";
17 | import Layout from "../../../components/layouts/RootLayout";
18 | import initPocketBase from "../../../helpers/initPocketbase";
19 | import DashboardLayout from "../../../components/layouts/DashboardLayout";
20 | import PageHeading from "../../../components/common/PageHeading";
21 | import Tooltip from "../../../components/ui/Tooltip";
22 | import Popover from "../../../components/ui/Popover";
23 | import ColorSwitcher from "../../../components/interactive/ColorSwitcher";
24 | import PageSection from "../../../components/common/PageSection";
25 | import PageHead from "../../../components/common/PageHead";
26 |
27 | // Import types
28 | import type { ReactElement } from "react";
29 | import type { GetServerSidePropsContext } from "next";
30 | import type { authData } from "../../../types/user";
31 |
32 | // Import icons
33 | import { IoWarningOutline } from "react-icons/io5";
34 | import { AiOutlineUserDelete } from "react-icons/ai";
35 |
36 | // Fire the dashboard settings page
37 | export default function DashboardSettings({
38 | isLoggedIn,
39 | authData,
40 | roles,
41 | }: {
42 | isLoggedIn: boolean;
43 | authData: authData;
44 | roles: string[];
45 | }) {
46 | const { t } = useTranslation("common");
47 | const router = useRouter();
48 |
49 | // Set states
50 | const [showEmail, setShowEmail] = useState(authData.emailVisibility);
51 | const [showOnline, setShowOnline] = useState(authData.showOnline);
52 | const [selectedLocale, setSelectedLocale] = useState([router.locale || "en"]);
53 | const [showOnlinePressed, setShowOnlinePressed] = useState(false);
54 | const [showEmailPressed, setShowEmailPressed] = useState(false);
55 | const [isDeletePopoverOpen, setIsDeletePopoverOpen] = useState(false);
56 |
57 | // Connect to database
58 | const pb = new PocketBase(process.env.NEXT_PUBLIC_API_URL);
59 |
60 | return (
61 | <>
62 |
63 |
64 |
65 |
66 | {/* Show content if logged in */}
67 | {isLoggedIn && (
68 | <>
69 | {/* Section: Privacy Settings */}
70 |
71 |
72 |
73 |
78 | {
85 | setShowEmailPressed(true);
86 | setShowEmail(!showEmail);
87 | await pb.collection("users").update(authData.id, {
88 | emailVisibility: !showEmail,
89 | });
90 | setTimeout(() => {
91 | setShowEmailPressed(false);
92 | }, 3000);
93 | }}
94 | />
95 |
96 | Show my email adress
97 |
98 |
99 |
100 |
105 | {
112 | setShowOnlinePressed(true);
113 | setShowOnline(!showOnline);
114 | await pb.collection("users").update(authData.id, {
115 | showOnline: !showOnline,
116 | });
117 | router.push(router.asPath, undefined, {
118 | locale: router.locale,
119 | });
120 | setTimeout(() => {
121 | setShowOnlinePressed(false);
122 | }, 3000);
123 | }}
124 | />
125 |
126 | Show my online status
127 |
128 |
129 |
130 |
131 | {/* Section: Other Settings */}
132 |
133 |
134 |
135 |
136 | Dark Interface
137 |
138 |
139 |
140 | Choose your prefered language
141 |
142 |
143 |
144 | {
150 | setLanguage(selectedLocale[0]);
151 | }}
152 | >
153 | {router.locales?.map((locale) => {
154 | let text = "";
155 |
156 | switch (locale) {
157 | case "en":
158 | text = "English";
159 | break;
160 | case "de":
161 | text = "Deutsch";
162 | }
163 |
164 | return (
165 | setSelectedLocale([locale])}
171 | >
172 | {text}
173 |
174 | );
175 | })}
176 |
177 |
178 |
179 |
180 |
181 | {/* Section: Delete my Account */}
182 |
183 |
191 | Caution! This can´t be undone!
192 |
193 |
194 |
195 |
196 |
197 | Please delete your account only if you really don't plan
198 | to come back. Your data will be deleted completely and
199 | irrevocably! It was nice having you here. See you around.
200 |
201 |
202 |
203 |
204 | {/* */}
205 | }
208 | isOpen={isDeletePopoverOpen}
209 | setIsOpen={setIsDeletePopoverOpen}
210 | width="25rem"
211 | >
212 |
213 | Confirm
214 |
215 |
216 |
217 | Please delete your account only if you really don't
218 | plan to come back. Your data will be deleted completely
219 | and irrevocably! It was nice having you here. See you
220 | around.
221 |
222 |
223 |
228 |
237 |
250 |
251 |
252 |
253 |
254 | >
255 | )}
256 |
257 | {/* For security: Not show content if not logged in */}
258 | {!isLoggedIn && (
259 |
260 | You are not logged in!
261 |
262 | )}
263 |
264 | >
265 | );
266 | }
267 |
268 | // Export serverside props
269 | export async function getServerSideProps(context: GetServerSidePropsContext) {
270 | // Get the language cookie
271 | const langCookie = getCookie("NEXT_LOCALE", context.res);
272 |
273 | // Init PocketBase
274 | const pb = await initPocketBase(context);
275 |
276 | // Strip the authData from the pb authStore
277 | const authData = JSON.parse(JSON.stringify(pb.authStore));
278 |
279 | let userRoles;
280 | let roles;
281 |
282 | // If the user is not logged in, redirect to the login page
283 | if (!pb.authStore.isValid) {
284 | return {
285 | redirect: {
286 | destination: "/login",
287 | permanent: false,
288 | },
289 | };
290 | }
291 |
292 | // If the language cookie is not the actual language, redirect to correct language
293 | if (langCookie && langCookie !== context.locale) {
294 | return {
295 | redirect: {
296 | destination: "/" + langCookie + context.resolvedUrl,
297 | permanent: false,
298 | },
299 | };
300 | }
301 |
302 | // Get the user roles
303 | await pb
304 | .collection("users")
305 | .getOne(authData.baseModel.id, {
306 | expand: "roles",
307 | })
308 | .then((res) => {
309 | userRoles = res.expand.roles;
310 | roles = userRoles.map((role: { name: string }) => role.name);
311 | })
312 | .catch(() => {
313 | roles = [];
314 | });
315 |
316 | // Return the props if user is logged in and correct language is set
317 | return {
318 | props: {
319 | isLoggedIn: pb.authStore.isValid,
320 | authData: authData.baseModel,
321 | roles: roles,
322 | },
323 | };
324 | }
325 |
326 | DashboardSettings.getLayout = function getLayout(page: ReactElement) {
327 | return (
328 |
329 | {page}
330 |
331 | );
332 | };
333 |
--------------------------------------------------------------------------------
/pages/index.tsx:
--------------------------------------------------------------------------------
1 | //Import components
2 | import Head from "next/head";
3 | import { Card, Container, Grid, Text, useTheme } from "@nextui-org/react";
4 | import useTranslation from "next-translate/useTranslation";
5 | import { getCookie } from "cookies-next";
6 | import Layout from "../components/layouts/RootLayout";
7 | import initPocketBase from "../helpers/initPocketbase";
8 | import LanguageSwitcher from "../components/interactive/LanguageSwitcher";
9 |
10 | // Import icons
11 | import { SiTailwindcss } from "react-icons/si";
12 | import { AiOutlineApi, AiOutlineHtml5 } from "react-icons/ai";
13 | import { FaReact, FaWpforms } from "react-icons/fa";
14 | import { MdOutlineDarkMode } from "react-icons/md";
15 | import { BsTranslate } from "react-icons/bs";
16 |
17 | // Import types
18 | import type { ReactElement } from "react";
19 | import type { GetServerSidePropsContext } from "next";
20 |
21 | // Fire the Home page
22 | export default function Home() {
23 | const { t } = useTranslation("common");
24 |
25 | const MockItems = ({
26 | text,
27 | icon,
28 | title,
29 | }: {
30 | text: string;
31 | icon: React.ReactNode;
32 | title: string;
33 | }) => {
34 | return (
35 |
36 |
37 |
38 | {icon}
39 |
40 |
48 | {title}
49 |
50 |
51 |
52 |
53 |
54 | {text}
55 |
56 |
57 | );
58 | };
59 |
60 | const { theme } = useTheme();
61 |
62 | return (
63 | <>
64 |
65 | {t("sitename")}
66 |
67 |
68 |
69 |
73 | {t("title")}
74 |
75 |
76 |
77 |
78 |
79 |
90 | }
91 | title="Next.js"
92 | />
93 |
94 |
95 |
106 | }
107 | title="Pocketpase"
108 | />
109 |
110 |
111 |
122 | }
123 | title="Tailwind & NextUI"
124 | />
125 |
126 |
127 |
138 | }
139 | title="React-Icons"
140 | />
141 |
142 |
143 |
154 | }
155 | title="Dark Mode"
156 | />
157 |
158 |
159 |
170 | }
171 | title="Translation"
172 | />
173 |
174 |
175 |
186 | }
187 | title="Formik - Coming Soon"
188 | />
189 |
190 |
191 |
192 |
193 | >
194 | );
195 | }
196 |
197 | // Export serverside props
198 | export async function getServerSideProps(context: GetServerSidePropsContext) {
199 | // Get the language cookie
200 | const langCookie = getCookie("NEXT_LOCALE", context.res);
201 |
202 | // Init PocketBase
203 | const pb = await initPocketBase(context);
204 |
205 | // Strip the authData from the pb authStore
206 | const authData = JSON.parse(JSON.stringify(pb.authStore));
207 |
208 | // If the language cookie is not the actual language, redirect to correct language
209 | if (langCookie && langCookie !== context.locale) {
210 | return {
211 | redirect: {
212 | destination: "/" + langCookie + context.resolvedUrl,
213 | permanent: false,
214 | },
215 | };
216 | }
217 |
218 | // Return the props if the correct language is set
219 | return {
220 | props: {
221 | isLoggedIn: pb.authStore.isValid,
222 | authData: authData.baseModel,
223 | },
224 | };
225 | }
226 |
227 | Home.getLayout = function getLayout(page: ReactElement) {
228 | return {page};
229 | };
230 |
--------------------------------------------------------------------------------
/pages/login/index.tsx:
--------------------------------------------------------------------------------
1 | // Import components
2 | import { useEffect, useState } from "react";
3 | import { useRouter } from "next/router";
4 | import Head from "next/head";
5 | import Link from "next/link";
6 | import PocketBase, { type AuthProviderInfo } from "pocketbase";
7 | import initPocketBase from "../../helpers/initPocketbase";
8 | import Layout from "../../components/layouts/RootLayout";
9 | import useTranslation from "next-translate/useTranslation";
10 | import { setCookie, getCookie, deleteCookie } from "cookies-next";
11 | import { Button, Container } from "@nextui-org/react";
12 |
13 | // Import types
14 | import type { ReactElement } from "react";
15 | import type { GetServerSidePropsContext } from "next";
16 | import type { authData } from "../../types/user";
17 |
18 | // Import icons
19 | import { FaTwitch, FaDiscord } from "react-icons/fa";
20 |
21 | // Fire the site
22 | export default function LoginPage({
23 | isLoggedIn,
24 | authData,
25 | }: {
26 | isLoggedIn: boolean;
27 | authData: authData;
28 | }) {
29 | const { t } = useTranslation("common");
30 | const router = useRouter();
31 | const [allProvider, setAllProvider] = useState();
32 |
33 | // Fire login logic on mount
34 | useEffect(() => {
35 | // Init PocketBase
36 | const pb = new PocketBase(process.env.NEXT_PUBLIC_API_URL);
37 |
38 | // Get the saved provider data from localstorage
39 | (async () => {
40 | const fromStorage = JSON.parse(localStorage.getItem("provider") || "{}");
41 |
42 | // Fires when the user comes back from the oauth2 provider
43 | if (router.query.code) {
44 | const code = router.query.code.toString();
45 |
46 | // Login the user
47 | await pb
48 | .collection("users")
49 | .authWithOAuth2(
50 | fromStorage.name,
51 | code,
52 | fromStorage.codeVerifier,
53 | process.env.NEXT_PUBLIC_SITE_URL + "/login",
54 | {
55 | banned: false,
56 | showOnline: true,
57 | lastActive: new Date(),
58 | emailVisibility: false,
59 | verified: true,
60 | }
61 | )
62 | .then((response) => {
63 | // Get the authStore from localstorage
64 | const authStoreLocalStorage =
65 | localStorage.getItem("pocketbase_auth");
66 |
67 | // Update the user data with the data from the oauth2 provider
68 | const dataToAdd = {
69 | avatarUrl: response.meta?.avatarUrl,
70 | lastActive: new Date(),
71 | };
72 | pb.collection("users").update(response.record.id, dataToAdd);
73 |
74 | // Remove the provider data from localstorage
75 | localStorage.removeItem("provider");
76 |
77 | if (authStoreLocalStorage) {
78 | // Set the authStore cookie
79 | setCookie("pb_auth", authStoreLocalStorage, {
80 | maxAge: 60 * 60 * 24 * 14,
81 | path: "/",
82 | secure: true,
83 | sameSite: "strict",
84 | });
85 |
86 | //Remove the authStore from localstorage
87 | localStorage.removeItem("pocketbase_auth");
88 |
89 | // Refresh page to login the user with the new cookie
90 | router.push("/login", "/login", {
91 | locale: router.locale,
92 | });
93 | }
94 | })
95 | // BACKLOG: Add error handling
96 | .catch((error) => {
97 | console.error(error);
98 | });
99 |
100 | // When the user is not logged in and it is not a redirect from the oauth2 provider
101 | } else if (!router.query.code && !isLoggedIn) {
102 | // Get the enabled oauth2 providers
103 | (async () => {
104 | const authMethods = await pb.collection("users").listAuthMethods();
105 | setAllProvider(authMethods.authProviders);
106 | })();
107 | }
108 | })();
109 |
110 | // eslint-disable-next-line react-hooks/exhaustive-deps
111 | }, []);
112 |
113 | return (
114 | <>
115 |
116 | {t("sitename") + " | Login"}
117 |
118 |
119 |
120 | {!router.query.code ? Login
: Wait for redirect...
}
121 | {allProvider && !isLoggedIn && (
122 |
123 | {allProvider.map((provider) => (
124 |
130 | ) : (
131 |
132 | )
133 | }
134 | onClick={() => {
135 | localStorage.setItem("provider", JSON.stringify(provider));
136 | router.push(
137 | provider.authUrl +
138 | process.env.NEXT_PUBLIC_SITE_URL +
139 | "/login"
140 | );
141 | }}
142 | >
143 | {provider.name.charAt(0).toUpperCase() + provider.name.slice(1)}{" "}
144 | Login
145 |
146 | ))}
147 |
148 | )}
149 |
150 | >
151 | );
152 | }
153 |
154 | // Export serverside props
155 | export async function getServerSideProps(context: GetServerSidePropsContext) {
156 | // Get language cookie for correct redirect path
157 | const languageCookie = getCookie("NEXT_LOCALE", context);
158 |
159 | // Get the redirect after login cookie
160 | const redirectAfterLoginCookie = getCookie("redirect_after_login", context);
161 |
162 | // Init PocketBase
163 | const pb = await initPocketBase(context);
164 |
165 | // Strip the authData from the pb authStore
166 | const authData = JSON.parse(JSON.stringify(pb.authStore));
167 |
168 | // Redirect if the user is logged in
169 | if (pb.authStore.isValid) {
170 | // Remove the redirect after login cookie
171 | deleteCookie("redirect_after_login", context);
172 |
173 | // Handle the redirect string
174 | const redirect = redirectAfterLoginCookie ? redirectAfterLoginCookie : "/";
175 | const language = languageCookie ? languageCookie : "en";
176 |
177 | return {
178 | redirect: {
179 | destination: "/" + language + redirect,
180 | permanent: false,
181 | },
182 | };
183 | }
184 |
185 | // If the language cookie is not the actual language, redirect to correct language
186 | if (languageCookie && languageCookie !== context.locale) {
187 | return {
188 | redirect: {
189 | destination: "/" + languageCookie + context.resolvedUrl,
190 | permanent: false,
191 | },
192 | };
193 | }
194 |
195 | // Return the props when user is not logged in and correct language is set
196 | return {
197 | props: {
198 | isLoggedIn: pb.authStore.isValid,
199 | authData: authData.baseModel,
200 | },
201 | };
202 | }
203 |
204 | // Load the layout
205 | LoginPage.getLayout = function getLayout(page: ReactElement) {
206 | return {page};
207 | };
208 |
--------------------------------------------------------------------------------
/pages/user/[slug]/edit/index.tsx:
--------------------------------------------------------------------------------
1 | //Import components
2 | import Head from "next/head";
3 | import { Container, Text } from "@nextui-org/react";
4 | import useTranslation from "next-translate/useTranslation";
5 | import { getCookie } from "cookies-next";
6 | import Layout from "../../../../components/layouts/RootLayout";
7 | import initPocketBase from "../../../../helpers/initPocketbase";
8 |
9 | // Import types
10 | import type { ReactElement } from "react";
11 | import type { GetServerSidePropsContext } from "next";
12 |
13 | // Fire the Home page
14 | export default function UserEdit() {
15 | const { t } = useTranslation("common");
16 |
17 | return (
18 | <>
19 |
20 | {t("sitename") + " | Edit User"}
21 |
22 |
23 |
24 | Edit User
25 |
26 | Content
27 | >
28 | );
29 | }
30 |
31 | // Export serverside props
32 | export async function getServerSideProps(context: GetServerSidePropsContext) {
33 | // Get the language cookie
34 | const langCookie = getCookie("NEXT_LOCALE", context.res);
35 |
36 | // Init PocketBase
37 | const pb = await initPocketBase(context);
38 |
39 | // Strip the authData from the pb authStore
40 | const authData = JSON.parse(JSON.stringify(pb.authStore));
41 |
42 | // If the language cookie is not the actual language, redirect to correct language
43 | if (langCookie && langCookie !== context.locale) {
44 | return {
45 | redirect: {
46 | destination: "/" + langCookie + context.resolvedUrl,
47 | permanent: false,
48 | },
49 | };
50 | }
51 |
52 | // Return the props if the correct language is set
53 | return {
54 | props: {
55 | isLoggedIn: pb.authStore.isValid,
56 | authData: authData.baseModel,
57 | },
58 | };
59 | }
60 |
61 | UserEdit.getLayout = function getLayout(page: ReactElement) {
62 | return {page};
63 | };
64 |
--------------------------------------------------------------------------------
/pages/user/[slug]/index.tsx:
--------------------------------------------------------------------------------
1 | //Import components
2 | import Head from "next/head";
3 | import { Container, Text } from "@nextui-org/react";
4 | import useTranslation from "next-translate/useTranslation";
5 | import { getCookie } from "cookies-next";
6 | import Layout from "../../../components/layouts/RootLayout";
7 | import initPocketBase from "../../../helpers/initPocketbase";
8 |
9 | // Import types
10 | import type { ReactElement } from "react";
11 | import type { GetServerSidePropsContext } from "next";
12 |
13 | // Fire the Home page
14 | export default function UserProfile() {
15 | const { t } = useTranslation("common");
16 |
17 | return (
18 | <>
19 |
20 | {t("sitename") + " | User Profile"}
21 |
22 |
23 |
24 | User Profile
25 |
26 | Content
27 | >
28 | );
29 | }
30 |
31 | // Export serverside props
32 | export async function getServerSideProps(context: GetServerSidePropsContext) {
33 | // Get the language cookie
34 | const langCookie = getCookie("NEXT_LOCALE", context.res);
35 |
36 | // Init PocketBase
37 | const pb = await initPocketBase(context);
38 |
39 | // Strip the authData from the pb authStore
40 | const authData = JSON.parse(JSON.stringify(pb.authStore));
41 |
42 | // If the language cookie is not the actual language, redirect to correct language
43 | if (langCookie && langCookie !== context.locale) {
44 | return {
45 | redirect: {
46 | destination: "/" + langCookie + context.resolvedUrl,
47 | permanent: false,
48 | },
49 | };
50 | }
51 |
52 | // Return the props if the correct language is set
53 | return {
54 | props: {
55 | isLoggedIn: pb.authStore.isValid,
56 | authData: authData.baseModel,
57 | },
58 | };
59 | }
60 |
61 | UserProfile.getLayout = function getLayout(page: ReactElement) {
62 | return {page};
63 | };
64 |
--------------------------------------------------------------------------------
/pages/user/index.tsx:
--------------------------------------------------------------------------------
1 | //Import components
2 | import Head from "next/head";
3 | import { Container, Text } from "@nextui-org/react";
4 | import useTranslation from "next-translate/useTranslation";
5 | import Layout from "../../components/layouts/RootLayout";
6 | import initPocketBase from "../../helpers/initPocketbase";
7 | import { getCookie } from "cookies-next";
8 |
9 | // Import types
10 | import type { ReactElement } from "react";
11 | import type { GetServerSidePropsContext } from "next";
12 |
13 | // Fire the Home page
14 | export default function User() {
15 | const { t } = useTranslation("common");
16 |
17 | return (
18 | <>
19 |
20 | {t("sitename") + " | User"}
21 |
22 |
23 |
24 | User
25 |
26 | Content
27 | >
28 | );
29 | }
30 |
31 | // Export serverside props
32 | export async function getServerSideProps(context: GetServerSidePropsContext) {
33 | // Get the language cookie
34 | const langCookie = getCookie("NEXT_LOCALE", context.res);
35 |
36 | // Init PocketBase
37 | const pb = await initPocketBase(context);
38 |
39 | // Strip the authData from the pb authStore
40 | const authData = JSON.parse(JSON.stringify(pb.authStore));
41 |
42 | // If the language cookie is not the actual language, redirect to correct language
43 | if (langCookie && langCookie !== context.locale) {
44 | return {
45 | redirect: {
46 | destination: "/" + langCookie + context.resolvedUrl,
47 | permanent: false,
48 | },
49 | };
50 | }
51 |
52 | // Return the props if the correct language is set
53 | return {
54 | props: {
55 | isLoggedIn: pb.authStore.isValid,
56 | authData: authData.baseModel,
57 | },
58 | };
59 | }
60 |
61 | User.getLayout = function getLayout(page: ReactElement) {
62 | return {page};
63 | };
64 |
--------------------------------------------------------------------------------
/pages/user/me/edit/index.tsx:
--------------------------------------------------------------------------------
1 | //Import components
2 | import Head from "next/head";
3 | import { Container, Text } from "@nextui-org/react";
4 | import useTranslation from "next-translate/useTranslation";
5 | import Layout from "../../../../components/layouts/RootLayout";
6 | import initPocketBase from "../../../../helpers/initPocketbase";
7 | import { getCookie } from "cookies-next";
8 |
9 | // Import types
10 | import type { ReactElement } from "react";
11 | import type { GetServerSidePropsContext } from "next";
12 |
13 | // Fire the Home page
14 | export default function MeEdit() {
15 | const { t } = useTranslation("common");
16 |
17 | return (
18 | <>
19 |
20 | {t("sitename") + " | Edit my profile"}
21 |
22 |
23 |
24 | Edit my profile
25 |
26 | Content
27 | >
28 | );
29 | }
30 |
31 | // Export serverside props
32 | export async function getServerSideProps(context: GetServerSidePropsContext) {
33 | // Get the language cookie
34 | const langCookie = getCookie("NEXT_LOCALE", context.res);
35 |
36 | // Init PocketBase
37 | const pb = await initPocketBase(context);
38 |
39 | // Strip the authData from the pb authStore
40 | const authData = JSON.parse(JSON.stringify(pb.authStore));
41 |
42 | // If the language cookie is not the actual language, redirect to correct language
43 | if (langCookie && langCookie !== context.locale) {
44 | return {
45 | redirect: {
46 | destination: "/" + langCookie + context.resolvedUrl,
47 | permanent: false,
48 | },
49 | };
50 | }
51 |
52 | // Return the props if the correct language is set
53 | return {
54 | props: {
55 | isLoggedIn: pb.authStore.isValid,
56 | authData: authData.baseModel,
57 | },
58 | };
59 | }
60 |
61 | MeEdit.getLayout = function getLayout(page: ReactElement) {
62 | return {page};
63 | };
64 |
--------------------------------------------------------------------------------
/pages/user/me/index.tsx:
--------------------------------------------------------------------------------
1 | //Import components
2 | import Head from "next/head";
3 | import { Container, Text } from "@nextui-org/react";
4 | import useTranslation from "next-translate/useTranslation";
5 | import { getCookie } from "cookies-next";
6 | import Layout from "../../../components/layouts/RootLayout";
7 | import initPocketBase from "../../../helpers/initPocketbase";
8 |
9 | // Import types
10 | import type { ReactElement } from "react";
11 | import type { GetServerSidePropsContext } from "next";
12 |
13 | // Fire the Home page
14 | export default function MyProfile() {
15 | const { t } = useTranslation("common");
16 |
17 | return (
18 | <>
19 |
20 | {t("sitename") + " | My Profile"}
21 |
22 |
23 |
24 | My Profile
25 |
26 | Content
27 | >
28 | );
29 | }
30 |
31 | // Export serverside props
32 | export async function getServerSideProps(context: GetServerSidePropsContext) {
33 | // Get the language cookie
34 | const langCookie = getCookie("NEXT_LOCALE", context.res);
35 |
36 | // Init PocketBase
37 | const pb = await initPocketBase(context);
38 |
39 | // Strip the authData from the pb authStore
40 | const authData = JSON.parse(JSON.stringify(pb.authStore));
41 |
42 | // If the language cookie is not the actual language, redirect to correct language
43 | if (langCookie && langCookie !== context.locale) {
44 | return {
45 | redirect: {
46 | destination: "/" + langCookie + context.resolvedUrl,
47 | permanent: false,
48 | },
49 | };
50 | }
51 |
52 | // Return the props if the correct language is set
53 | return {
54 | props: {
55 | isLoggedIn: pb.authStore.isValid,
56 | authData: authData.baseModel,
57 | },
58 | };
59 | }
60 |
61 | MyProfile.getLayout = function getLayout(page: ReactElement) {
62 | return {page};
63 | };
64 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChristianSeelemann/nextjs-with-pocketbase/455e145f295c7cfed384257d8dfab12c2a954751/public/favicon.ico
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind components;
2 | @tailwind utilities;
3 |
4 | /* ===== Scrollbar CSS ===== */
5 | /* Firefox */
6 | * {
7 | scrollbar-width: auto;
8 | scrollbar-color: #abc8ff2a transparent;
9 | }
10 |
11 | body {
12 | overflow-y: scroll !important;
13 | }
14 |
15 | html.dark-theme * {
16 | scrollbar-color: #abc8ff2a transparent;
17 | }
18 |
19 | /* Chrome, Edge, and Safari */
20 | *::-webkit-scrollbar {
21 | width: 0.4rem;
22 | }
23 |
24 | *::-webkit-scrollbar-track {
25 | background: transparent;
26 | }
27 |
28 | *::-webkit-scrollbar-thumb {
29 | background-color: #3d41494d;
30 | border-radius: 12px;
31 | }
32 |
33 | html.dark-theme *::-webkit-scrollbar-thumb {
34 | background-color: #97a1b44d;
35 | border-radius: 12px;
36 | }
37 |
38 | .nextui-navbar-collapse-wrapper {
39 | overflow: hidden;
40 | }
41 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | "./pages/**/*.{js,ts,jsx,tsx}",
5 | "./components/**/*.{js,ts,jsx,tsx}",
6 | ],
7 | theme: {
8 | extend: {
9 | fontFamily: {
10 | primary: ["Bai Jamjuree", "sans-serif"],
11 | secondary: ["Chakra Petch", "sans-serif"],
12 | },
13 | },
14 | screens: {
15 | xs: "650px",
16 | sm: "960px",
17 | md: "1280px",
18 | lg: "1400px",
19 | xl: "1920px",
20 | },
21 | },
22 | plugins: [],
23 | };
24 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true
17 | },
18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
19 | "exclude": ["node_modules"]
20 | }
21 |
--------------------------------------------------------------------------------
/types/user.ts:
--------------------------------------------------------------------------------
1 | export interface authData {
2 | id: string;
3 | username: string;
4 | name: string | null;
5 | avatar: string | null;
6 | avatarUrl: string | null;
7 | banned: boolean;
8 | bannedUntil: Date | null;
9 | bannedReason: string | null;
10 | collectionId: string;
11 | collectionName: string;
12 | created: string;
13 | email: string | null;
14 | emailVisibility: boolean;
15 | firstName: string | null;
16 | lastName: string | null;
17 | lastActive: Date;
18 | roles: string[] | null;
19 | showOnline: boolean;
20 | updated: string;
21 | verified: boolean;
22 | }
23 |
--------------------------------------------------------------------------------