mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-06 18:31:00 +00:00
Added glow on profile, added isAdmin hook and blocked components / pages that nly admins should access
This commit is contained in:
parent
aaad0d9931
commit
c7f98fcf5d
@ -14,7 +14,7 @@ const BottomBar = () => {
|
|||||||
<div onClick={() => router.push('/')} className={`hover:bg-gray-700 cursor-pointer px-4 py-3 rounded-lg ${isActive('/') ? 'bg-gray-700' : ''}`}>
|
<div onClick={() => router.push('/')} className={`hover:bg-gray-700 cursor-pointer px-4 py-3 rounded-lg ${isActive('/') ? 'bg-gray-700' : ''}`}>
|
||||||
<i className="pi pi-home text-2xl" />
|
<i className="pi pi-home text-2xl" />
|
||||||
</div>
|
</div>
|
||||||
<div onClick={() => router.push('/content')} className={`hover:bg-gray-700 cursor-pointer px-4 py-3 rounded-lg ${isActive('/content') ? 'bg-gray-700' : ''}`}>
|
<div onClick={() => router.push('/content?tag=all')} className={`hover:bg-gray-700 cursor-pointer px-4 py-3 rounded-lg ${isActive('/content') ? 'bg-gray-700' : ''}`}>
|
||||||
<i className="pi pi-video text-2xl" />
|
<i className="pi pi-video text-2xl" />
|
||||||
</div>
|
</div>
|
||||||
<div onClick={() => router.push('/feed?channel=global')} className={`hover:bg-gray-700 cursor-pointer px-4 py-3 rounded-lg ${isActive('/feed') ? 'bg-gray-700' : ''}`}>
|
<div onClick={() => router.push('/feed?channel=global')} className={`hover:bg-gray-700 cursor-pointer px-4 py-3 rounded-lg ${isActive('/feed') ? 'bg-gray-700' : ''}`}>
|
||||||
|
@ -9,10 +9,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
border-radius: 25px;
|
border-radius: 50%;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
max-height: 60px;
|
height: 50px;
|
||||||
max-width: 60px;
|
width: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React, { useRef, useState, useEffect } from 'react';
|
import React, { useRef, useState, useEffect } from 'react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
import { Avatar } from 'primereact/avatar';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useImageProxy } from '@/hooks/useImageProxy';
|
import { useImageProxy } from '@/hooks/useImageProxy';
|
||||||
import GenericButton from '@/components/buttons/GenericButton';
|
import GenericButton from '@/components/buttons/GenericButton';
|
||||||
@ -7,6 +8,8 @@ import { Menu } from 'primereact/menu';
|
|||||||
import useWindowWidth from '@/hooks/useWindowWidth';
|
import useWindowWidth from '@/hooks/useWindowWidth';
|
||||||
import { useSession, signOut } from 'next-auth/react';
|
import { useSession, signOut } from 'next-auth/react';
|
||||||
import { Dialog } from 'primereact/dialog';
|
import { Dialog } from 'primereact/dialog';
|
||||||
|
import { ProgressSpinner } from 'primereact/progressspinner';
|
||||||
|
import { useIsAdmin } from '@/hooks/useIsAdmin';
|
||||||
import 'primereact/resources/primereact.min.css';
|
import 'primereact/resources/primereact.min.css';
|
||||||
import 'primeicons/primeicons.css';
|
import 'primeicons/primeicons.css';
|
||||||
import styles from '../navbar.module.css';
|
import styles from '../navbar.module.css';
|
||||||
@ -16,9 +19,10 @@ const UserAvatar = () => {
|
|||||||
const [isClient, setIsClient] = useState(false);
|
const [isClient, setIsClient] = useState(false);
|
||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null);
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
|
const [isProfile, setIsProfile] = useState(false);
|
||||||
const { returnImageProxy } = useImageProxy();
|
const { returnImageProxy } = useImageProxy();
|
||||||
const windowWidth = useWindowWidth();
|
const windowWidth = useWindowWidth();
|
||||||
|
const { isAdmin, isLoading } = useIsAdmin();
|
||||||
const { data: session, status } = useSession();
|
const { data: session, status } = useSession();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -28,6 +32,14 @@ const UserAvatar = () => {
|
|||||||
}
|
}
|
||||||
}, [session]);
|
}, [session]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (router.asPath === '/profile?tab=profile') {
|
||||||
|
setIsProfile(true);
|
||||||
|
} else {
|
||||||
|
setIsProfile(false);
|
||||||
|
}
|
||||||
|
}, [router.asPath]);
|
||||||
|
|
||||||
const menu = useRef(null);
|
const menu = useRef(null);
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
@ -57,11 +69,12 @@ const UserAvatar = () => {
|
|||||||
icon: 'pi pi-user',
|
icon: 'pi pi-user',
|
||||||
command: () => router.push('/profile?tab=profile')
|
command: () => router.push('/profile?tab=profile')
|
||||||
},
|
},
|
||||||
{
|
// Only show the "Create" option for admin users
|
||||||
|
...(isAdmin ? [{
|
||||||
label: 'Create',
|
label: 'Create',
|
||||||
icon: 'pi pi-book',
|
icon: 'pi pi-book',
|
||||||
command: () => router.push('/create')
|
command: () => router.push('/create')
|
||||||
},
|
}] : []),
|
||||||
{
|
{
|
||||||
label: 'Logout',
|
label: 'Logout',
|
||||||
icon: 'pi pi-power-off',
|
icon: 'pi pi-power-off',
|
||||||
@ -72,13 +85,17 @@ const UserAvatar = () => {
|
|||||||
];
|
];
|
||||||
userAvatar = (
|
userAvatar = (
|
||||||
<>
|
<>
|
||||||
<div onClick={(event) => menu.current.toggle(event)} className='flex flex-row items-center justify-between cursor-pointer hover:opacity-75'>
|
<div onClick={(event) => menu.current.toggle(event)} className={`flex flex-row items-center justify-between cursor-pointer hover:opacity-75`}>
|
||||||
<Image
|
<Image
|
||||||
alt="logo"
|
alt="logo"
|
||||||
src={returnImageProxy(user.avatar, user.pubkey)}
|
src={returnImageProxy(user.avatar, user.pubkey)}
|
||||||
width={50}
|
width={50}
|
||||||
height={50}
|
height={50}
|
||||||
className={styles.logo}
|
className={styles.logo}
|
||||||
|
style={{
|
||||||
|
boxShadow: isProfile ? '0 0 10px 3px rgba(255, 255, 255, 0.7)' : 'none',
|
||||||
|
transition: 'box-shadow 0.3s ease-in-out',
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Menu model={items} popup ref={menu} className='w-[250px] break-words' />
|
<Menu model={items} popup ref={menu} className='w-[250px] break-words' />
|
||||||
|
@ -2,13 +2,14 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { Accordion, AccordionTab } from 'primereact/accordion';
|
import { Accordion, AccordionTab } from 'primereact/accordion';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useSession, signOut } from 'next-auth/react';
|
import { useSession, signOut } from 'next-auth/react';
|
||||||
import { Button } from 'primereact/button';
|
import { useIsAdmin } from '@/hooks/useIsAdmin';
|
||||||
import 'primeicons/primeicons.css';
|
import 'primeicons/primeicons.css';
|
||||||
import styles from "./sidebar.module.css";
|
import styles from "./sidebar.module.css";
|
||||||
import { Divider } from 'primereact/divider';
|
import { Divider } from 'primereact/divider';
|
||||||
|
|
||||||
const Sidebar = () => {
|
const Sidebar = () => {
|
||||||
const [isExpanded, setIsExpanded] = useState(true);
|
const [isExpanded, setIsExpanded] = useState(true);
|
||||||
|
const { isAdmin } = useIsAdmin();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// Helper function to determine if the path matches the current route
|
// Helper function to determine if the path matches the current route
|
||||||
@ -61,9 +62,11 @@ const Sidebar = () => {
|
|||||||
</div>
|
</div>
|
||||||
</AccordionTab>
|
</AccordionTab>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<div onClick={() => 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' : ''}`}>
|
{isAdmin && (
|
||||||
<i className="pi pi-plus pl-5 text-sm" /> <p className="pl-2 rounded-md font-bold text-lg">Create</p>
|
<div onClick={() => 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' : ''}`}>
|
||||||
</div>
|
<i className="pi pi-plus pl-5 text-sm" /> <p className="pl-2 rounded-md font-bold text-lg">Create</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div onClick={() => 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' : ''}`}>
|
<div onClick={() => 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' : ''}`}>
|
||||||
<i className="pi pi-star pl-5 text-sm" /> <p className="pl-2 rounded-md font-bold text-lg">Subscribe</p>
|
<i className="pi pi-star pl-5 text-sm" /> <p className="pl-2 rounded-md font-bold text-lg">Subscribe</p>
|
||||||
</div>
|
</div>
|
||||||
|
16
src/db/models/roleModels.js
Normal file
16
src/db/models/roleModels.js
Normal file
@ -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,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
17
src/hooks/useIsAdmin.js
Normal file
17
src/hooks/useIsAdmin.js
Normal file
@ -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' };
|
||||||
|
}
|
@ -9,6 +9,7 @@ import { findKind0Fields } from "@/utils/nostr";
|
|||||||
import { generateSecretKey, getPublicKey } from 'nostr-tools/pure'
|
import { generateSecretKey, getPublicKey } from 'nostr-tools/pure'
|
||||||
import { bytesToHex } from '@noble/hashes/utils'
|
import { bytesToHex } from '@noble/hashes/utils'
|
||||||
import { updateUser } from "@/db/models/userModels";
|
import { updateUser } from "@/db/models/userModels";
|
||||||
|
import { createRole } from "@/db/models/roleModels";
|
||||||
|
|
||||||
const relayUrls = [
|
const relayUrls = [
|
||||||
"wss://nos.lol/",
|
"wss://nos.lol/",
|
||||||
@ -21,6 +22,7 @@ const relayUrls = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const BASE_URL = process.env.BASE_URL;
|
const BASE_URL = process.env.BASE_URL;
|
||||||
|
const AUTHOR_PUBKEY = process.env.AUTHOR_PUBKEY;
|
||||||
|
|
||||||
const ndk = new NDK({
|
const ndk = new NDK({
|
||||||
explicitRelayUrls: relayUrls,
|
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
|
// Add combined user object to the token
|
||||||
if (user) {
|
if (user) {
|
||||||
token.user = user;
|
token.user = user;
|
||||||
|
@ -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) {
|
export default async function handler(req, res) {
|
||||||
try {
|
try {
|
||||||
|
27
src/pages/api/roles/index.js
Normal file
27
src/pages/api/roles/index.js
Normal file
@ -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" });
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +1,30 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import MenuTab from "@/components/menutab/MenuTab";
|
import MenuTab from "@/components/menutab/MenuTab";
|
||||||
import ResourceForm from "@/components/forms/ResourceForm";
|
import ResourceForm from "@/components/forms/ResourceForm";
|
||||||
import WorkshopForm from "@/components/forms/WorkshopForm";
|
import WorkshopForm from "@/components/forms/WorkshopForm";
|
||||||
import CourseForm from "@/components/forms/course/CourseForm";
|
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 Create = () => {
|
||||||
const [activeIndex, setActiveIndex] = useState(0); // State to track the active tab index
|
const [activeIndex, setActiveIndex] = useState(0); // State to track the active tab index
|
||||||
|
const { isAdmin, isLoading } = useIsAdmin();
|
||||||
|
const router = useRouter();
|
||||||
const homeItems = [
|
const homeItems = [
|
||||||
{ label: 'Resource', icon: 'pi pi-book' },
|
{ label: 'Resource', icon: 'pi pi-book' },
|
||||||
{ label: 'Workshop', icon: 'pi pi-video' },
|
{ label: 'Workshop', icon: 'pi pi-video' },
|
||||||
{ label: 'Course', icon: 'pi pi-desktop' }
|
{ 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
|
// Function to render the correct form based on the active tab
|
||||||
const renderForm = () => {
|
const renderForm = () => {
|
||||||
switch (homeItems[activeIndex].label) {
|
switch (homeItems[activeIndex].label) {
|
||||||
@ -26,6 +39,10 @@ const Create = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!isAdmin) return null;
|
||||||
|
|
||||||
|
if (isLoading) return <ProgressSpinner />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full min-bottom-bar:w-[86vw] max-sidebar:w-[100vw] px-8 mx-auto my-8 flex flex-col justify-center">
|
<div className="w-full min-bottom-bar:w-[86vw] max-sidebar:w-[100vw] px-8 mx-auto my-8 flex flex-col justify-center">
|
||||||
<h2 className="text-center mb-8">Create a {homeItems[activeIndex].label}</h2>
|
<h2 className="text-center mb-8">Create a {homeItems[activeIndex].label}</h2>
|
||||||
|
@ -5,10 +5,15 @@ import UserSettings from "@/components/profile/UserSettings";
|
|||||||
import UserContent from "@/components/profile/UserContent";
|
import UserContent from "@/components/profile/UserContent";
|
||||||
import UserSubscription from "@/components/profile/subscription/UserSubscription";
|
import UserSubscription from "@/components/profile/subscription/UserSubscription";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { useSession } from "next-auth/react";
|
||||||
|
import { useIsAdmin } from "@/hooks/useIsAdmin";
|
||||||
|
import { ProgressSpinner } from "primereact/progressspinner";
|
||||||
|
|
||||||
const Profile = () => {
|
const Profile = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { data: session, status } = useSession();
|
||||||
const [activeTab, setActiveTab] = useState(0);
|
const [activeTab, setActiveTab] = useState(0);
|
||||||
|
const {isAdmin, isLoading} = useIsAdmin();
|
||||||
|
|
||||||
const tabs = ["profile", "settings", "content", "subscribe"];
|
const tabs = ["profile", "settings", "content", "subscribe"];
|
||||||
|
|
||||||
@ -22,12 +27,31 @@ const Profile = () => {
|
|||||||
}
|
}
|
||||||
}, [router.query]);
|
}, [router.query]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (status === 'unauthenticated') {
|
||||||
|
router.push('/auth/signin');
|
||||||
|
}
|
||||||
|
}, [status, router]);
|
||||||
|
|
||||||
const onTabChange = (e) => {
|
const onTabChange = (e) => {
|
||||||
const newIndex = e.index;
|
const newIndex = e.index;
|
||||||
setActiveTab(newIndex);
|
setActiveTab(newIndex);
|
||||||
router.push(`/profile?tab=${tabs[newIndex]}`, undefined, { shallow: true });
|
router.push(`/profile?tab=${tabs[newIndex]}`, undefined, { shallow: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (status === 'loading' || isLoading) {
|
||||||
|
return (
|
||||||
|
<ProgressSpinner />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === 'unauthenticated') {
|
||||||
|
router.push('/auth/signin');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!session) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full min-h-full min-bottom-bar:w-[86vw] mx-auto">
|
<div className="w-full min-h-full min-bottom-bar:w-[86vw] mx-auto">
|
||||||
<TabView
|
<TabView
|
||||||
@ -56,13 +80,15 @@ const Profile = () => {
|
|||||||
}}>
|
}}>
|
||||||
<UserSettings />
|
<UserSettings />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel header="Content" pt={{
|
{isAdmin && (
|
||||||
headerAction: {
|
<TabPanel header="Content" pt={{
|
||||||
|
headerAction: {
|
||||||
className: "bg-transparent"
|
className: "bg-transparent"
|
||||||
},
|
},
|
||||||
}}>
|
}}>
|
||||||
<UserContent />
|
<UserContent />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
)}
|
||||||
<TabPanel header="Subscribe" pt={{
|
<TabPanel header="Subscribe" pt={{
|
||||||
headerAction: {
|
headerAction: {
|
||||||
className: "bg-transparent"
|
className: "bg-transparent"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user