mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-03 15:52:03 +00:00
Full login /signup flow working, basic profile page and user avatar, pulling in resources from nostr and rendering
This commit is contained in:
parent
16513c71ec
commit
595c553dd2
@ -2,7 +2,7 @@
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
images: {
|
||||
domains: ['fakestoreapi.com'],
|
||||
domains: ['localhost'],
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ CREATE TABLE "User" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"pubkey" TEXT NOT NULL,
|
||||
"username" TEXT,
|
||||
"avatar" TEXT,
|
||||
"roleId" INTEGER,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
@ -11,6 +11,7 @@ model User {
|
||||
id Int @id @default(autoincrement())
|
||||
pubkey String @unique
|
||||
username String? @unique
|
||||
avatar String?
|
||||
purchased Purchase[]
|
||||
role Role? @relation(fields: [roleId], references: [id])
|
||||
roleId Int?
|
||||
|
@ -1,32 +1,17 @@
|
||||
import React from 'react';
|
||||
import React, {useRef} from 'react';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Button } from 'primereact/button';
|
||||
import UserAvatar from './user/UserAvatar';
|
||||
import { Menubar } from 'primereact/menubar';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRouter } from 'next/router';
|
||||
import 'primereact/resources/primereact.min.css';
|
||||
import 'primeicons/primeicons.css';
|
||||
import styles from './navbar.module.css';
|
||||
|
||||
const Navbar = () => {
|
||||
const router = useRouter();
|
||||
const user = useSelector((state) => state.user);
|
||||
|
||||
const end = (
|
||||
(user && user?.username || user.pubkey) ?
|
||||
<h1>{user.username || user.pubkey}</h1>
|
||||
:
|
||||
<Button
|
||||
label={"Login"}
|
||||
icon="pi pi-user"
|
||||
className="text-[#f8f8ff]"
|
||||
rounded
|
||||
onClick={() => router.push('/login')}
|
||||
/>
|
||||
);
|
||||
|
||||
const start = (
|
||||
<div className={styles.titleContainer}>
|
||||
<div onClick={() => router.push('/')} className={styles.titleContainer}>
|
||||
<Image
|
||||
alt="logo"
|
||||
src="/plebdevs-guy.jpg"
|
||||
@ -39,7 +24,7 @@ const Navbar = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<Menubar start={start} end={end} className='px-[5%]' />
|
||||
<Menubar start={start} end={UserAvatar} className='px-[5%]' />
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
justify-content: center;
|
||||
padding-left: 6%;
|
||||
padding-right: 6%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.logo {
|
||||
|
81
src/components/navbar/user/UserAvatar.js
Normal file
81
src/components/navbar/user/UserAvatar.js
Normal file
@ -0,0 +1,81 @@
|
||||
import React, {useRef} from 'react';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useImageProxy } from '@/hooks/useImageProxy';
|
||||
import { Button } from 'primereact/button';
|
||||
import { Menu } from 'primereact/menu';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { setUser } from '@/redux/reducers/userReducer';
|
||||
import 'primereact/resources/primereact.min.css';
|
||||
import 'primeicons/primeicons.css';
|
||||
import styles from '../navbar.module.css';
|
||||
|
||||
const UserAvatar = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useDispatch();
|
||||
const user = useSelector((state) => state.user.user);
|
||||
const { returnImageProxy } = useImageProxy();
|
||||
|
||||
const menu = useRef(null);
|
||||
|
||||
const handleLogout = () => {
|
||||
window.localStorage.removeItem('pubkey');
|
||||
dispatch(setUser(null));
|
||||
router.push('/');
|
||||
}
|
||||
|
||||
let userAvatar;
|
||||
|
||||
if (user) {
|
||||
// User exists, show username or pubkey
|
||||
const displayName = user.username || user.pubkey;
|
||||
|
||||
const items = [
|
||||
{
|
||||
label: displayName,
|
||||
items: [
|
||||
{
|
||||
label: 'Profile',
|
||||
icon: 'pi pi-user',
|
||||
command: () => router.push('/profile')
|
||||
},
|
||||
{
|
||||
label: 'Logout',
|
||||
icon: 'pi pi-power-off',
|
||||
command: handleLogout
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
userAvatar = (
|
||||
<>
|
||||
<div onClick={(event) => menu.current.toggle(event)} className='flex flex-row items-center justify-between cursor-pointer hover:opacity-75'>
|
||||
{user.avatar && (
|
||||
<Image
|
||||
alt="logo"
|
||||
src={returnImageProxy(user.avatar)}
|
||||
width={50}
|
||||
height={50}
|
||||
className={styles.logo}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Menu model={items} popup ref={menu} />
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
userAvatar = (
|
||||
<Button
|
||||
label="Login"
|
||||
icon="pi pi-user"
|
||||
className="text-[#f8f8ff]"
|
||||
rounded
|
||||
onClick={() => router.push('/login')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return userAvatar;
|
||||
};
|
||||
|
||||
export default UserAvatar;
|
@ -3,60 +3,29 @@ import React, { useState, useEffect } from 'react';
|
||||
import { Button } from 'primereact/button';
|
||||
import { Carousel } from 'primereact/carousel';
|
||||
import { Tag } from 'primereact/tag';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { parseResourceEvent } from '@/utils/nostr';
|
||||
|
||||
export default function ResourcesCarousel() {
|
||||
const [courses, setCourses] = useState([
|
||||
{
|
||||
"title": "Lightning Wallet Frontend",
|
||||
"description": "Write your first code and learn Frontend from scratch to build a simple lightning wallet using HTML/CSS, Javascript, and React",
|
||||
"thumbnail": 'https://emeralize.s3.amazonaws.com/course/cover_images/plebdev2_750__422_px_1200__630_px.jpg',
|
||||
"price": 45000
|
||||
},
|
||||
{
|
||||
"title": "Lightning Wallet Backend",
|
||||
"description": "Learn Backend from scratch and build a simple Lightning Wallet backend with a server, API, Database, and Lightning node using NodeJS",
|
||||
"thumbnail": 'https://emeralize.s3.amazonaws.com/course/cover_images/plebdevs-thumbnail.png',
|
||||
"price": 70000
|
||||
},
|
||||
{
|
||||
"title": "Lightning Wallet Frontend",
|
||||
"description": "Write your first code and learn Frontend from scratch to build a simple lightning wallet using HTML/CSS, Javascript, and React",
|
||||
"thumbnail": 'https://emeralize.s3.amazonaws.com/course/cover_images/plebdev2_750__422_px_1200__630_px.jpg',
|
||||
"price": 45000
|
||||
},
|
||||
{
|
||||
"title": "Lightning Wallet Backend",
|
||||
"description": "Learn Backend from scratch and build a simple Lightning Wallet backend with a server, API, Database, and Lightning node using NodeJS",
|
||||
"thumbnail": 'https://emeralize.s3.amazonaws.com/course/cover_images/plebdevs-thumbnail.png',
|
||||
"price": 70000
|
||||
},
|
||||
{
|
||||
"title": "Lightning Wallet Frontend",
|
||||
"description": "Write your first code and learn Frontend from scratch to build a simple lightning wallet using HTML/CSS, Javascript, and React",
|
||||
"thumbnail": 'https://emeralize.s3.amazonaws.com/course/cover_images/plebdev2_750__422_px_1200__630_px.jpg',
|
||||
"price": 45000
|
||||
},
|
||||
{
|
||||
"title": "Lightning Wallet Backend",
|
||||
"description": "Learn Backend from scratch and build a simple Lightning Wallet backend with a server, API, Database, and Lightning node using NodeJS",
|
||||
"thumbnail": 'https://emeralize.s3.amazonaws.com/course/cover_images/plebdevs-thumbnail.png',
|
||||
"price": 70000
|
||||
}
|
||||
]);
|
||||
const resources = useSelector((state) => state.events.resources);
|
||||
|
||||
const productTemplate = (course) => {
|
||||
console.log('Resources:', resources);
|
||||
|
||||
const resourceTemplate = (resource) => {
|
||||
const { content, title, summary, image, published_at } = parseResourceEvent(resource);
|
||||
return (
|
||||
<div className="flex flex-col items-center w-full px-4">
|
||||
<div className="w-86 h-60 bg-gray-200 overflow-hidden rounded-md shadow-lg">
|
||||
<img src={course.thumbnail} alt={course.title} className="w-full h-full object-cover object-center" />
|
||||
<img src={image} alt={title} className="w-full h-full object-cover object-center" />
|
||||
</div>
|
||||
<div className='text-center'>
|
||||
<h4 className="mb-1 text-center">{course.title}</h4>
|
||||
<h6 className="mt-0 mb-3 text-center">{course.price} sats</h6>
|
||||
<h4 className="mb-1 font-bold text-center">{title}</h4>
|
||||
<p className="text-center">{summary}</p>
|
||||
<div className="flex flex-row items-center justify-center gap-2">
|
||||
<Button icon="pi pi-search" rounded />
|
||||
<Button icon="pi pi-star-fill" rounded severity="success" />
|
||||
</div>
|
||||
<p className="text-center mt-2">Published on {published_at}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -65,7 +34,7 @@ export default function ResourcesCarousel() {
|
||||
return (
|
||||
<>
|
||||
<h1 className="text-2xl font-bold ml-[6%] my-4">Resources</h1>
|
||||
<Carousel value={courses} numVisible={3} itemTemplate={productTemplate} />
|
||||
<Carousel value={resources} numVisible={3} itemTemplate={resourceTemplate} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
12
src/hooks/useImageProxy.js
Normal file
12
src/hooks/useImageProxy.js
Normal file
@ -0,0 +1,12 @@
|
||||
import React from "react"
|
||||
|
||||
export const useImageProxy = () => {
|
||||
|
||||
const returnImageProxy = (image) => {
|
||||
const proxyUrl = `${process.env.NEXT_PUBLIC_PROXY_URL}?imageUrl=${encodeURIComponent(image)}`;
|
||||
|
||||
return proxyUrl;
|
||||
}
|
||||
|
||||
return { returnImageProxy };
|
||||
}
|
@ -1,16 +1,18 @@
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useNostr } from './useNostr';
|
||||
import axios from 'axios';
|
||||
import { setPubkey, setUsername } from "@/redux/reducers/userReducer";
|
||||
import { setUser } from "@/redux/reducers/userReducer";
|
||||
import { generateSecretKey, getPublicKey } from 'nostr-tools';
|
||||
import { findKind0Username } from "@/utils/nostr";
|
||||
import { findKind0Fields } from "@/utils/nostr";
|
||||
import { useToast } from './useToast';
|
||||
|
||||
export const useLogin = () => {
|
||||
const dispatch = useDispatch();
|
||||
const router = useRouter();
|
||||
const { showToast } = useToast();
|
||||
const { fetchKind0 } = useNostr();
|
||||
|
||||
// Attempt Auto Login on render
|
||||
useEffect(() => {
|
||||
@ -22,11 +24,26 @@ export const useLogin = () => {
|
||||
try {
|
||||
const response = await axios.get(`/api/users/${publicKey}`);
|
||||
if (response.status === 200 && response.data) {
|
||||
dispatch(setPubkey(publicKey));
|
||||
if (response.data.username) {
|
||||
dispatch(setUsername(response.data.username));
|
||||
dispatch(setUser(response.data));
|
||||
} else if (response.status === 204) {
|
||||
// User not found, create a new user
|
||||
const kind0 = await fetchKind0([{ authors: [publicKey], kinds: [0] }], {});
|
||||
const fields = await findKind0Fields(kind0);
|
||||
const payload = { pubkey: publicKey, ...fields };
|
||||
|
||||
try {
|
||||
const createUserResponse = await axios.post(`/api/users`, payload);
|
||||
if (createUserResponse.status === 201) {
|
||||
;
|
||||
window.localStorage.setItem('pubkey', publicKey);
|
||||
dispatch(setUser(createUserResponse.data));
|
||||
} else {
|
||||
console.error('Error creating user:', createUserResponse);
|
||||
}
|
||||
} catch (createError) {
|
||||
console.error('Error creating user:', createError);
|
||||
showToast('error', 'Error Creating User', 'Failed to create user');
|
||||
}
|
||||
router.push('/');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error during auto login:', error);
|
||||
@ -52,22 +69,21 @@ export const useLogin = () => {
|
||||
const response = await axios.get(`/api/users/${publicKey}`);
|
||||
if (response.status !== 200) throw new Error('User not found');
|
||||
|
||||
dispatch(setPubkey(publicKey));
|
||||
window.localStorage.setItem('pubkey', publicKey);
|
||||
if (response.data.username) dispatch(setUsername(response.data.username));
|
||||
dispatch(setUser(response.data));
|
||||
router.push('/');
|
||||
} catch (error) {
|
||||
// User not found, create a new user
|
||||
const kind0 = await findKind0Username({ authors: [publicKey], kinds: [0] }); // Adjust based on actual implementation
|
||||
const username = kind0 ? kind0 : undefined;
|
||||
const payload = { pubkey: publicKey, ...(username && { username }) };
|
||||
const kind0 = await fetchKind0([{ authors: [publicKey], kinds: [0] }], {});
|
||||
const fields = await findKind0Fields(kind0);
|
||||
const payload = { pubkey: publicKey, ...fields };
|
||||
|
||||
try {
|
||||
const createUserResponse = await axios.post(`/api/users`, payload);
|
||||
if (createUserResponse.status === 201) {
|
||||
dispatch(setPubkey(publicKey));
|
||||
;
|
||||
window.localStorage.setItem('pubkey', publicKey);
|
||||
if (username) dispatch(setUsername(username));
|
||||
dispatch(setUser(createUserResponse.data));
|
||||
router.push('/');
|
||||
} else {
|
||||
console.error('Error creating user:', createUserResponse);
|
||||
@ -77,14 +93,14 @@ export const useLogin = () => {
|
||||
showToast('error', 'Error Creating User', 'Failed to create user');
|
||||
}
|
||||
}
|
||||
}, [dispatch, router, showToast]);
|
||||
}, [dispatch, router, showToast, fetchKind0]);
|
||||
|
||||
const anonymousLogin = useCallback(() => {
|
||||
try {
|
||||
const secretKey = generateSecretKey();
|
||||
const publicKey = getPublicKey(secretKey);
|
||||
|
||||
dispatch(setPubkey(publicKey));
|
||||
|
||||
dispatch(setUser({ pubkey: publicKey }));
|
||||
window.localStorage.setItem('pubkey', publicKey);
|
||||
window.localStorage.setItem('seckey', secretKey);
|
||||
router.push('/');
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { SimplePool, relayInit, nip19 } from "nostr-tools";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { addResource } from "@/redux/reducers/eventsReducer";
|
||||
import { initialRelays } from "@/redux/reducers/userReducer";
|
||||
|
||||
export const useNostr = () => {
|
||||
@ -61,6 +62,36 @@ export const useNostr = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const fetchResources = async () => {
|
||||
const filter = [{kinds: [30023], authors: ["f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741"]}];
|
||||
|
||||
const params = {seenOnEnabled: true};
|
||||
const seenEventIds = new Set(); // Set to keep track of event IDs that have been processed
|
||||
|
||||
const hasPlebdevsTag = (eventData) => {
|
||||
return eventData.some(([tag, value]) => tag === "t" && value === "plebdevs");
|
||||
};
|
||||
|
||||
const sub = pool.current.subscribeMany(relays, filter, {
|
||||
...params,
|
||||
onevent: (event) => {
|
||||
// Assuming event has a unique identifier under `id` field
|
||||
if (!seenEventIds.has(event.id) && hasPlebdevsTag(event.tags)) {
|
||||
seenEventIds.add(event.id); // Add event ID to the set to track it's been processed
|
||||
dispatch(addResource(event));
|
||||
}
|
||||
},
|
||||
onerror: (error) => {
|
||||
console.error("Error fetching resources:", error);
|
||||
},
|
||||
oneose: () => {
|
||||
console.log("Subscription closed");
|
||||
sub.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const fetchSingleEvent = async (id) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const sub = pool.current.subscribeMany(relays, [{ ids: [id] }]);
|
||||
@ -101,6 +132,7 @@ export const useNostr = () => {
|
||||
fetchSingleEvent,
|
||||
publishEvent,
|
||||
fetchKind0,
|
||||
fetchResources,
|
||||
getRelayStatuses,
|
||||
};
|
||||
};
|
@ -3,7 +3,7 @@ import { Provider } from "react-redux";
|
||||
import { store } from "@/redux/store";
|
||||
import Navbar from '@/components/navbar/Navbar';
|
||||
import { ToastProvider } from '@/hooks/useToast';
|
||||
import Layout from '@/components/layout';
|
||||
import Layout from '@/components/Layout';
|
||||
import '@/styles/globals.css'
|
||||
import 'primereact/resources/themes/lara-dark-indigo/theme.css';
|
||||
|
||||
|
27
src/pages/api/image-proxy.js
Normal file
27
src/pages/api/image-proxy.js
Normal file
@ -0,0 +1,27 @@
|
||||
import axios from 'axios';
|
||||
|
||||
export default async function handler(req, res) {
|
||||
const { imageUrl } = req.query;
|
||||
|
||||
// Validate the imageUrl query parameter
|
||||
if (!imageUrl) {
|
||||
return res.status(400).json({ error: 'Missing imageUrl query parameter' });
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios({
|
||||
method: 'GET',
|
||||
url: imageUrl,
|
||||
responseType: 'stream',
|
||||
});
|
||||
|
||||
// Forward the content type
|
||||
res.setHeader('Content-Type', response.headers['content-type']);
|
||||
|
||||
// Stream the image from the external source to the client
|
||||
response.data.pipe(res);
|
||||
} catch (error) {
|
||||
console.error('Image proxy error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch image' });
|
||||
}
|
||||
}
|
@ -1,8 +1,17 @@
|
||||
import Head from 'next/head'
|
||||
import React, {useCallback, useEffect, useState} from 'react';
|
||||
import CoursesCarousel from '@/components/courses/CoursesCarousel'
|
||||
import ResourcesCarousel from '@/components/resources/ResourcesCarousel'
|
||||
import { useNostr } from '@/hooks/useNostr'
|
||||
|
||||
export default function Home() {
|
||||
const { fetchResources } = useNostr();
|
||||
|
||||
useEffect(() => {
|
||||
console.log('Fetching resources');
|
||||
fetchResources();
|
||||
}, [fetchResources]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
|
55
src/pages/profile.js
Normal file
55
src/pages/profile.js
Normal file
@ -0,0 +1,55 @@
|
||||
import React from "react";
|
||||
import { Button } from "primereact/button";
|
||||
import { DataTable } from 'primereact/datatable';
|
||||
import { Column } from 'primereact/column';
|
||||
import { useSelector } from "react-redux";
|
||||
import { useImageProxy } from "@/hooks/useImageProxy";
|
||||
import Image from "next/image";
|
||||
|
||||
const Profile = () => {
|
||||
const user = useSelector((state) => state.user.user);
|
||||
const { returnImageProxy } = useImageProxy();
|
||||
|
||||
const purchases = [
|
||||
{ code: '123', name: 'Product 1', category: 'Category 1', quantity: 1 },
|
||||
{ code: '124', name: 'Product 2', category: 'Category 2', quantity: 2 },
|
||||
{ code: '125', name: 'Product 3', category: 'Category 3', quantity: 3 },
|
||||
{ code: '126', name: 'Product 4', category: 'Category 4', quantity: 4 },
|
||||
{ code: '127', name: 'Product 5', category: 'Category 5', quantity: 5 },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col justify-center">
|
||||
{user.avatar && (
|
||||
<Image
|
||||
alt="logo"
|
||||
src={returnImageProxy(user.avatar)}
|
||||
width={100}
|
||||
height={100}
|
||||
className="rounded-full mx-auto my-4"
|
||||
/>
|
||||
)}
|
||||
<h1 className="text-center text-2xl my-2">{user.username}</h1>
|
||||
<h2 className="text-center text-xl my-2">{user.pubkey}</h2>
|
||||
<div className="flex flex-row w-1/2 mx-auto my-4 justify-between">
|
||||
<Button label="Edit" className="p-button-raised text-[#f8f8ff]" />
|
||||
<Button label="Delete" className="p-button-raised p-button-danger text-[#f8f8ff]" />
|
||||
</div>
|
||||
<div className="flex flex-col w-1/2 mx-auto my-4 justify-between items-center">
|
||||
<h2>Subscription</h2>
|
||||
<p>You currently have no active subscription</p>
|
||||
<Button label="Subscribe" className="p-button-raised p-button-success w-auto my-2 text-[#f8f8ff]" />
|
||||
</div>
|
||||
</div>
|
||||
<DataTable value={purchases} tableStyle={{ minWidth: '50rem' }}>
|
||||
<Column field="code" header="Code"></Column>
|
||||
<Column field="name" header="Name"></Column>
|
||||
<Column field="category" header="Category"></Column>
|
||||
<Column field="quantity" header="Quantity"></Column>
|
||||
</DataTable>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Profile
|
@ -1,13 +1,13 @@
|
||||
import { createSlice, current } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
// Helper function to add single or multiple items
|
||||
const addItems = (state, action, key) => {
|
||||
const items = current(state)[key];
|
||||
if (Array.isArray(action.payload)) {
|
||||
// If payload is an array, spread it into the existing array
|
||||
state[key] = [...items, ...action.payload];
|
||||
state[key] = [...state[key], ...action.payload];
|
||||
} else {
|
||||
// If payload is a single item, push it into the array
|
||||
items.push(action.payload);
|
||||
state[key].push(action.payload);
|
||||
}
|
||||
};
|
||||
|
||||
@ -18,16 +18,22 @@ export const eventsSlice = createSlice({
|
||||
courses: [],
|
||||
},
|
||||
reducers: {
|
||||
setItems: (state, action) => {
|
||||
if (action.payload.type === 'resource') {
|
||||
addItems(state, action.payload.data, 'resources');
|
||||
} else if (action.payload.type === 'course') {
|
||||
addItems(state, action.payload.data, 'courses');
|
||||
}
|
||||
addResource: (state, action) => {
|
||||
addItems(state, action, 'resources');
|
||||
},
|
||||
addCourse: (state, action) => {
|
||||
addItems(state, action, 'courses');
|
||||
},
|
||||
// If you need to set the entire array at once, keep these or adjust as necessary
|
||||
setResources: (state, action) => {
|
||||
state.resources = action.payload;
|
||||
},
|
||||
setCourses: (state, action) => {
|
||||
state.courses = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Exports
|
||||
export const { setItems } = eventsSlice.actions;
|
||||
export const { addResource, addCourse, setResources, setCourses } = eventsSlice.actions;
|
||||
export default eventsSlice.reducer;
|
||||
|
@ -13,23 +13,19 @@ export const initialRelays = [
|
||||
export const userSlice = createSlice({
|
||||
name: "user",
|
||||
initialState: {
|
||||
pubkey: '',
|
||||
username: '',
|
||||
user: {},
|
||||
relays: initialRelays,
|
||||
},
|
||||
reducers: {
|
||||
setRelays: (state, action) => {
|
||||
state.relays = [...state.relays, action.payload];
|
||||
},
|
||||
setPubkey: (state, action) => {
|
||||
state.pubkey = action.payload;
|
||||
},
|
||||
setUsername: (state, action) => {
|
||||
state.username = action.payload;
|
||||
setUser: (state, action) => {
|
||||
state.user = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setRelays, setPubkey, setUsername } = userSlice.actions;
|
||||
export const { setRelays, setUser } = userSlice.actions;
|
||||
|
||||
export default userSlice.reducer;
|
@ -1,4 +1,6 @@
|
||||
export const findKind0Username = async (kind0) => {
|
||||
export const findKind0Fields = async (kind0) => {
|
||||
let fields = {}
|
||||
|
||||
const usernameProperties = ['name', 'displayName', 'display_name', 'username', 'handle', 'alias'];
|
||||
|
||||
const findTruthyPropertyValue = (object, properties) => {
|
||||
@ -12,5 +14,47 @@ export const findKind0Username = async (kind0) => {
|
||||
|
||||
const username = findTruthyPropertyValue(kind0, usernameProperties);
|
||||
|
||||
return username;
|
||||
}
|
||||
if (username) {
|
||||
fields.username = username;
|
||||
}
|
||||
|
||||
const avatar = findTruthyPropertyValue(kind0, ['picture', 'avatar', 'profilePicture', 'profile_picture']);
|
||||
|
||||
if (avatar) {
|
||||
fields.avatar = avatar;
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
export const parseResourceEvent = (event) => {
|
||||
// Initialize an object to store the extracted data
|
||||
const eventData = {
|
||||
content: event.content || '',
|
||||
title: '',
|
||||
summary: '',
|
||||
image: '',
|
||||
published_at: '',
|
||||
};
|
||||
|
||||
// Iterate over the tags array to extract data
|
||||
event.tags.forEach(tag => {
|
||||
switch (tag[0]) { // Check the key in each key-value pair
|
||||
case 'title':
|
||||
eventData.title = tag[1];
|
||||
break;
|
||||
case 'summary':
|
||||
eventData.summary = tag[1];
|
||||
break;
|
||||
case 'image':
|
||||
eventData.image = tag[1];
|
||||
break;
|
||||
case 'published_at':
|
||||
eventData.published_at = tag[1];
|
||||
break;
|
||||
// Add cases for any other data you need to extract
|
||||
}
|
||||
});
|
||||
|
||||
return eventData;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user