router.push('/content')} className={`hover:bg-gray-700 cursor-pointer px-4 py-3 rounded-lg ${isActive('/content') ? 'bg-gray-700' : ''}`}>
+
router.push('/content?tag=all')} className={`hover:bg-gray-700 cursor-pointer px-4 py-3 rounded-lg ${isActive('/content') ? 'bg-gray-700' : ''}`}>
router.push('/feed?channel=global')} className={`hover:bg-gray-700 cursor-pointer px-4 py-3 rounded-lg ${isActive('/feed') ? 'bg-gray-700' : ''}`}>
diff --git a/src/components/navbar/navbar.module.css b/src/components/navbar/navbar.module.css
index 6aa32f7..af80b05 100644
--- a/src/components/navbar/navbar.module.css
+++ b/src/components/navbar/navbar.module.css
@@ -9,10 +9,10 @@
}
.logo {
- border-radius: 25px;
+ border-radius: 50%;
margin-right: 8px;
- max-height: 60px;
- max-width: 60px;
+ height: 50px;
+ width: 50px;
}
.title {
diff --git a/src/components/navbar/user/UserAvatar.js b/src/components/navbar/user/UserAvatar.js
index 55f540c..e4af999 100644
--- a/src/components/navbar/user/UserAvatar.js
+++ b/src/components/navbar/user/UserAvatar.js
@@ -1,5 +1,6 @@
import React, { useRef, useState, useEffect } from 'react';
import Image from 'next/image';
+import { Avatar } from 'primereact/avatar';
import { useRouter } from 'next/router';
import { useImageProxy } from '@/hooks/useImageProxy';
import GenericButton from '@/components/buttons/GenericButton';
@@ -7,6 +8,8 @@ import { Menu } from 'primereact/menu';
import useWindowWidth from '@/hooks/useWindowWidth';
import { useSession, signOut } from 'next-auth/react';
import { Dialog } from 'primereact/dialog';
+import { ProgressSpinner } from 'primereact/progressspinner';
+import { useIsAdmin } from '@/hooks/useIsAdmin';
import 'primereact/resources/primereact.min.css';
import 'primeicons/primeicons.css';
import styles from '../navbar.module.css';
@@ -16,9 +19,10 @@ const UserAvatar = () => {
const [isClient, setIsClient] = useState(false);
const [user, setUser] = useState(null);
const [visible, setVisible] = useState(false);
+ const [isProfile, setIsProfile] = useState(false);
const { returnImageProxy } = useImageProxy();
const windowWidth = useWindowWidth();
-
+ const { isAdmin, isLoading } = useIsAdmin();
const { data: session, status } = useSession();
useEffect(() => {
@@ -28,6 +32,14 @@ const UserAvatar = () => {
}
}, [session]);
+ useEffect(() => {
+ if (router.asPath === '/profile?tab=profile') {
+ setIsProfile(true);
+ } else {
+ setIsProfile(false);
+ }
+ }, [router.asPath]);
+
const menu = useRef(null);
const handleLogout = async () => {
@@ -57,11 +69,12 @@ const UserAvatar = () => {
icon: 'pi pi-user',
command: () => router.push('/profile?tab=profile')
},
- {
+ // Only show the "Create" option for admin users
+ ...(isAdmin ? [{
label: 'Create',
icon: 'pi pi-book',
command: () => router.push('/create')
- },
+ }] : []),
{
label: 'Logout',
icon: 'pi pi-power-off',
@@ -72,13 +85,17 @@ const UserAvatar = () => {
];
userAvatar = (
<>
-
menu.current.toggle(event)} className='flex flex-row items-center justify-between cursor-pointer hover:opacity-75'>
+
menu.current.toggle(event)} className={`flex flex-row items-center justify-between cursor-pointer hover:opacity-75`}>
diff --git a/src/components/sidebar/Sidebar.js b/src/components/sidebar/Sidebar.js
index 91d6474..3b3842d 100644
--- a/src/components/sidebar/Sidebar.js
+++ b/src/components/sidebar/Sidebar.js
@@ -2,13 +2,14 @@ import React, { useState, useEffect } from 'react';
import { Accordion, AccordionTab } from 'primereact/accordion';
import { useRouter } from 'next/router';
import { useSession, signOut } from 'next-auth/react';
-import { Button } from 'primereact/button';
+import { useIsAdmin } from '@/hooks/useIsAdmin';
import 'primeicons/primeicons.css';
import styles from "./sidebar.module.css";
import { Divider } from 'primereact/divider';
const Sidebar = () => {
const [isExpanded, setIsExpanded] = useState(true);
+ const { isAdmin } = useIsAdmin();
const router = useRouter();
// Helper function to determine if the path matches the current route
@@ -61,9 +62,11 @@ const Sidebar = () => {
-
router.push('/create')} className={`w-full flex flex-row items-center cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/create') ? 'bg-gray-700' : ''}`}>
-
Create
-
+ {isAdmin && (
+
router.push('/create')} className={`w-full flex flex-row items-center cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/create') ? 'bg-gray-700' : ''}`}>
+
Create
+
+ )}
session ? router.push('/profile?tab=subscribe') : router.push('/auth/signin')} className={`w-full flex flex-row items-center cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/profile?tab=subscribe') ? 'bg-gray-700' : ''}`}>
Subscribe
diff --git a/src/db/models/roleModels.js b/src/db/models/roleModels.js
new file mode 100644
index 0000000..a367250
--- /dev/null
+++ b/src/db/models/roleModels.js
@@ -0,0 +1,16 @@
+import prisma from "../prisma";
+
+export const createRole = async (data) => {
+ return await prisma.role.create({
+ data: {
+ user: { connect: { id: data.userId } },
+ admin: data.admin,
+ subscribed: data.subscribed,
+ // Add other fields as needed, with default values or null if not provided
+ subscriptionStartDate: null,
+ lastPaymentAt: null,
+ subscriptionExpiredAt: null,
+ nwc: null,
+ }
+ });
+};
\ No newline at end of file
diff --git a/src/hooks/useIsAdmin.js b/src/hooks/useIsAdmin.js
new file mode 100644
index 0000000..8019947
--- /dev/null
+++ b/src/hooks/useIsAdmin.js
@@ -0,0 +1,17 @@
+import { useEffect, useState } from 'react';
+import { useSession } from 'next-auth/react';
+
+export function useIsAdmin() {
+ const { data: session, status } = useSession();
+ const [isAdmin, setIsAdmin] = useState(false);
+
+ useEffect(() => {
+ if (status === 'authenticated') {
+ setIsAdmin(session?.user?.role?.admin || false);
+ } else if (status === 'unauthenticated') {
+ setIsAdmin(false);
+ }
+ }, [session, status]);
+
+ return { isAdmin, isLoading: status === 'loading' };
+}
\ No newline at end of file
diff --git a/src/pages/api/auth/[...nextauth].js b/src/pages/api/auth/[...nextauth].js
index a8752ca..8322a4d 100644
--- a/src/pages/api/auth/[...nextauth].js
+++ b/src/pages/api/auth/[...nextauth].js
@@ -9,6 +9,7 @@ import { findKind0Fields } from "@/utils/nostr";
import { generateSecretKey, getPublicKey } from 'nostr-tools/pure'
import { bytesToHex } from '@noble/hashes/utils'
import { updateUser } from "@/db/models/userModels";
+import { createRole } from "@/db/models/roleModels";
const relayUrls = [
"wss://nos.lol/",
@@ -21,6 +22,7 @@ const relayUrls = [
];
const BASE_URL = process.env.BASE_URL;
+const AUTHOR_PUBKEY = process.env.AUTHOR_PUBKEY;
const ndk = new NDK({
explicitRelayUrls: relayUrls,
@@ -114,6 +116,27 @@ export const authOptions = {
}
}
+ if (user && user?.pubkey === AUTHOR_PUBKEY && !user?.role) {
+ // create a new author role for this user
+ const role = await createRole({
+ userId: user.id,
+ admin: true,
+ subscribed: false,
+ });
+
+ if (!role) {
+ console.error("Failed to create role");
+ return null;
+ }
+
+ const updatedUser = await updateUser(user.id, {role: role.id});
+ if (!updatedUser) {
+ console.error("Failed to update user");
+ return null;
+ }
+ token.user = updatedUser;
+ }
+
// Add combined user object to the token
if (user) {
token.user = user;
diff --git a/src/pages/api/get-video-url.js b/src/pages/api/get-video-url.js
index b3ae67d..0a0bc99 100644
--- a/src/pages/api/get-video-url.js
+++ b/src/pages/api/get-video-url.js
@@ -12,7 +12,7 @@ const s3Client = new S3Client({
},
})
-const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY
+const AUTHOR_PUBKEY = process.env.AUTHOR_PUBKEY
export default async function handler(req, res) {
try {
diff --git a/src/pages/api/roles/index.js b/src/pages/api/roles/index.js
new file mode 100644
index 0000000..6be0435
--- /dev/null
+++ b/src/pages/api/roles/index.js
@@ -0,0 +1,27 @@
+import { createRole } from "@/db/models/roleModels";
+
+export default async function handler(req, res) {
+ if (req.method === "POST") {
+ if (!req.body || !req.body.userId) {
+ res.status(400).json({ error: "Missing required fields" });
+ return;
+ }
+
+ try {
+ const roleData = {
+ userId: req.body.userId,
+ admin: req.body.admin || false,
+ subscribed: req.body.subscribed || false,
+ // Add other fields as needed
+ };
+
+ const role = await createRole(roleData);
+ res.status(201).json(role);
+ } catch (error) {
+ console.error("Error creating role:", error);
+ res.status(500).json({ error: "Error creating role" });
+ }
+ } else {
+ res.status(405).json({ error: "Method not allowed" });
+ }
+}
\ No newline at end of file
diff --git a/src/pages/create.js b/src/pages/create.js
index 8c27876..ff609f0 100644
--- a/src/pages/create.js
+++ b/src/pages/create.js
@@ -1,17 +1,30 @@
-import React, { useState } from "react";
+import React, { useState, useEffect } from "react";
import MenuTab from "@/components/menutab/MenuTab";
import ResourceForm from "@/components/forms/ResourceForm";
import WorkshopForm from "@/components/forms/WorkshopForm";
import CourseForm from "@/components/forms/course/CourseForm";
+import { useIsAdmin } from "@/hooks/useIsAdmin";
+import { useRouter } from "next/router";
+import { ProgressSpinner } from "primereact/progressspinner";
const Create = () => {
const [activeIndex, setActiveIndex] = useState(0); // State to track the active tab index
+ const { isAdmin, isLoading } = useIsAdmin();
+ const router = useRouter();
const homeItems = [
{ label: 'Resource', icon: 'pi pi-book' },
{ label: 'Workshop', icon: 'pi pi-video' },
{ label: 'Course', icon: 'pi pi-desktop' }
];
+ useEffect(() => {
+ if (isLoading) return;
+
+ if (!isAdmin) {
+ router.push('/');
+ }
+ }, [isAdmin, router, isLoading]);
+
// Function to render the correct form based on the active tab
const renderForm = () => {
switch (homeItems[activeIndex].label) {
@@ -26,6 +39,10 @@ const Create = () => {
}
};
+ if (!isAdmin) return null;
+
+ if (isLoading) return
;
+
return (
Create a {homeItems[activeIndex].label}
diff --git a/src/pages/profile.js b/src/pages/profile.js
index 82f04e4..6be3763 100644
--- a/src/pages/profile.js
+++ b/src/pages/profile.js
@@ -5,11 +5,16 @@ import UserSettings from "@/components/profile/UserSettings";
import UserContent from "@/components/profile/UserContent";
import UserSubscription from "@/components/profile/subscription/UserSubscription";
import { useRouter } from "next/router";
+import { useSession } from "next-auth/react";
+import { useIsAdmin } from "@/hooks/useIsAdmin";
+import { ProgressSpinner } from "primereact/progressspinner";
const Profile = () => {
const router = useRouter();
+ const { data: session, status } = useSession();
const [activeTab, setActiveTab] = useState(0);
-
+ const {isAdmin, isLoading} = useIsAdmin();
+
const tabs = ["profile", "settings", "content", "subscribe"];
useEffect(() => {
@@ -22,12 +27,31 @@ const Profile = () => {
}
}, [router.query]);
+ useEffect(() => {
+ if (status === 'unauthenticated') {
+ router.push('/auth/signin');
+ }
+ }, [status, router]);
+
const onTabChange = (e) => {
const newIndex = e.index;
setActiveTab(newIndex);
router.push(`/profile?tab=${tabs[newIndex]}`, undefined, { shallow: true });
};
+ if (status === 'loading' || isLoading) {
+ return (
+
+ );
+ }
+
+ if (status === 'unauthenticated') {
+ router.push('/auth/signin');
+ return null;
+ }
+
+ if (!session) return null;
+
return (
{
}}>
-
-
-
+
+
+ )}