mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-14 19:45:04 +00:00
first set of user progress flow fixes / refactor
This commit is contained in:
parent
5d1e9d32bb
commit
e8c5c161dd
@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useNDKContext } from '@/context/NDKContext';
|
import { useNDKContext } from '@/context/NDKContext';
|
||||||
import { parseEvent } from '@/utils/nostr';
|
import { parseEvent, parseCourseEvent } from '@/utils/nostr';
|
||||||
import { ProgressSpinner } from 'primereact/progressspinner';
|
import { ProgressSpinner } from 'primereact/progressspinner';
|
||||||
import { nip19 } from 'nostr-tools';
|
import { nip19 } from 'nostr-tools';
|
||||||
import appConfig from '@/config/appConfig';
|
import appConfig from '@/config/appConfig';
|
||||||
@ -8,50 +8,73 @@ import appConfig from '@/config/appConfig';
|
|||||||
const PurchasedListItem = ({ eventId, category }) => {
|
const PurchasedListItem = ({ eventId, category }) => {
|
||||||
const { ndk } = useNDKContext();
|
const { ndk } = useNDKContext();
|
||||||
const [event, setEvent] = useState(null);
|
const [event, setEvent] = useState(null);
|
||||||
const [naddr, setNaddr] = useState(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchEvent = async () => {
|
const fetchEvent = async () => {
|
||||||
if (!eventId) return;
|
if (!eventId || !ndk) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await ndk.connect();
|
const filter = category === 'courses'
|
||||||
const event = await ndk.fetchEvent(eventId);
|
? {
|
||||||
if (event) {
|
kinds: [30004],
|
||||||
setEvent(parseEvent(event));
|
authors: appConfig.authorPubkeys,
|
||||||
|
'#d': [eventId],
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
kinds: [30023, 30402],
|
||||||
|
authors: appConfig.authorPubkeys,
|
||||||
|
'#d': [eventId],
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchedEvent = await ndk.fetchEvent(filter);
|
||||||
|
if (fetchedEvent) {
|
||||||
|
setEvent(category === 'courses' ? parseCourseEvent(fetchedEvent) : parseEvent(fetchedEvent));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching event:', error);
|
console.error('Error fetching event in PurchasedListItem:', error);
|
||||||
|
setEvent(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
fetchEvent();
|
fetchEvent();
|
||||||
}, [eventId, ndk]);
|
}, [eventId, ndk, category]);
|
||||||
|
|
||||||
useEffect(() => {
|
const encodeNaddrForLink = () => {
|
||||||
if (event) {
|
if (!event || !event.pubkey || !event.d) return null;
|
||||||
encodeNaddr();
|
try {
|
||||||
}
|
return nip19.naddrEncode({
|
||||||
}, [event]);
|
|
||||||
|
|
||||||
const encodeNaddr = () => {
|
|
||||||
setNaddr(
|
|
||||||
nip19.naddrEncode({
|
|
||||||
pubkey: event.pubkey,
|
pubkey: event.pubkey,
|
||||||
identifier: event.d,
|
identifier: event.d,
|
||||||
kind: event.kind,
|
kind: category === 'courses' ? 30004 : event.kind,
|
||||||
relays: appConfig.defaultRelayUrls,
|
relays: appConfig.defaultRelayUrls,
|
||||||
})
|
});
|
||||||
);
|
} catch (error) {
|
||||||
|
console.error("Error encoding naddr:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return !event || !ndk ? (
|
if (!ndk) {
|
||||||
<ProgressSpinner className="w-[40px] h-[40px]" />
|
return <ProgressSpinner className="w-[40px] h-[40px]" title="Initializing..." />;
|
||||||
) : (
|
}
|
||||||
|
if (!eventId) {
|
||||||
|
return <span className="text-gray-500">Missing item ID</span>
|
||||||
|
}
|
||||||
|
if (!event) {
|
||||||
|
return <ProgressSpinner className="w-[40px] h-[40px]" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const naddrValue = encodeNaddrForLink();
|
||||||
|
|
||||||
|
if (!naddrValue) {
|
||||||
|
return <span className="text-red-500">Error generating link. (Invalid Event ID?)</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<a
|
<a
|
||||||
className="text-blue-500 underline hover:text-blue-600"
|
className="text-blue-500 underline hover:text-blue-600"
|
||||||
href={category === 'courses' ? `/course/${naddr}` : `/details/${naddr}`}
|
href={category === 'courses' ? `/course/${naddrValue}` : `/details/${naddrValue}`}
|
||||||
>
|
>
|
||||||
{event.title}
|
{event.name || event.title || 'Unnamed Item'}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { DataTable } from 'primereact/datatable';
|
import GenericDataTable from '@/components/ui/DataTables/DataTable';
|
||||||
import { Column } from 'primereact/column';
|
|
||||||
import ProgressListItem from '@/components/content/lists/ProgressListItem';
|
import ProgressListItem from '@/components/content/lists/ProgressListItem';
|
||||||
import { formatDateTime } from '@/utils/time';
|
import { formatDateTime } from '@/utils/time';
|
||||||
import { ProgressSpinner } from 'primereact/progressspinner';
|
import { ProgressSpinner } from 'primereact/progressspinner';
|
||||||
@ -92,7 +91,7 @@ const UserProgressTable = ({ session, ndk }) => {
|
|||||||
return progressData.sort((a, b) => new Date(b.date) - new Date(a.date));
|
return progressData.sort((a, b) => new Date(b.date) - new Date(a.date));
|
||||||
};
|
};
|
||||||
|
|
||||||
const header = (
|
const tableHeader = (
|
||||||
<div className="flex flex-wrap align-items-center justify-content-between gap-2">
|
<div className="flex flex-wrap align-items-center justify-content-between gap-2">
|
||||||
<span className="text-xl text-900 font-bold text-[#f8f8ff]">Progress</span>
|
<span className="text-xl text-900 font-bold text-[#f8f8ff]">Progress</span>
|
||||||
</div>
|
</div>
|
||||||
@ -161,6 +160,13 @@ const UserProgressTable = ({ session, ndk }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ field: 'type', header: 'Type', body: typeTemplate },
|
||||||
|
{ field: 'eventType', header: 'Event', body: eventTemplate },
|
||||||
|
{ field: 'name', header: 'Name', body: nameTemplate },
|
||||||
|
{ field: 'date', header: 'Date', body: dateTemplate },
|
||||||
|
];
|
||||||
|
|
||||||
if (!session || !session?.user || !ndk) {
|
if (!session || !session?.user || !ndk) {
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full flex items-center justify-center">
|
<div className="w-full h-full flex items-center justify-center">
|
||||||
@ -170,10 +176,11 @@ const UserProgressTable = ({ session, ndk }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataTable
|
<GenericDataTable
|
||||||
emptyMessage="No Courses or Milestones completed"
|
emptyMessage="No Courses or Milestones completed"
|
||||||
value={prepareProgressData()}
|
value={prepareProgressData()}
|
||||||
header={header}
|
header={tableHeader}
|
||||||
|
columns={columns}
|
||||||
className="mt-2 mx-2 max-lap:mx-0"
|
className="mt-2 mx-2 max-lap:mx-0"
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@ -199,12 +206,10 @@ const UserProgressTable = ({ session, ndk }) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
stripedRows
|
stripedRows
|
||||||
|
dataKey="id"
|
||||||
>
|
>
|
||||||
<Column field="type" header="Type" body={typeTemplate}></Column>
|
{/* Original Column definitions removed */}
|
||||||
<Column field="eventType" header="Event" body={eventTemplate}></Column>
|
</GenericDataTable>
|
||||||
<Column field="name" header="Name" body={nameTemplate}></Column>
|
|
||||||
<Column field="date" body={dateTemplate} header="Date"></Column>
|
|
||||||
</DataTable>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { DataTable } from 'primereact/datatable';
|
import GenericDataTable from '@/components/ui/DataTables/DataTable';
|
||||||
import { Column } from 'primereact/column';
|
|
||||||
import PurchasedListItem from '@/components/content/lists/PurchasedListItem';
|
import PurchasedListItem from '@/components/content/lists/PurchasedListItem';
|
||||||
import { formatDateTime } from '@/utils/time';
|
import { formatDateTime } from '@/utils/time';
|
||||||
|
|
||||||
@ -47,13 +46,22 @@ const UserPurchaseTable = ({ session, windowWidth }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Define columns for GenericDataTable
|
||||||
|
const columns = [
|
||||||
|
{ field: 'amountPaid', header: 'Cost', body: costTemplate },
|
||||||
|
{ field: 'name', header: 'Name', body: nameTemplate },
|
||||||
|
{ field: 'category', header: 'Category', body: categoryTemplate },
|
||||||
|
{ field: 'createdAt', header: 'Date', body: dateTemplate },
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
session &&
|
session &&
|
||||||
session?.user && (
|
session?.user && (
|
||||||
<DataTable
|
<GenericDataTable
|
||||||
emptyMessage="No purchases"
|
emptyMessage="No purchases"
|
||||||
value={session.user?.purchased}
|
value={session.user?.purchased}
|
||||||
header={purchasesHeader}
|
header={purchasesHeader}
|
||||||
|
columns={columns}
|
||||||
className="mt-2 mx-2 max-lap:mx-0"
|
className="mt-2 mx-2 max-lap:mx-0"
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@ -80,11 +88,8 @@ const UserPurchaseTable = ({ session, windowWidth }) => {
|
|||||||
}}
|
}}
|
||||||
stripedRows
|
stripedRows
|
||||||
>
|
>
|
||||||
<Column field="amountPaid" header="Cost" body={costTemplate}></Column>
|
{/* Original Column definitions removed */}
|
||||||
<Column field="name" header="Name" body={nameTemplate}></Column>
|
</GenericDataTable>
|
||||||
<Column field="category" header="Category" body={categoryTemplate}></Column>
|
|
||||||
<Column field="createdAt" header="Date" body={dateTemplate}></Column>
|
|
||||||
</DataTable>
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import { DataTable } from 'primereact/datatable';
|
import GenericDataTable from '@/components/ui/DataTables/DataTable';
|
||||||
import { Column } from 'primereact/column';
|
|
||||||
import { InputText } from 'primereact/inputtext';
|
import { InputText } from 'primereact/inputtext';
|
||||||
import GenericButton from '@/components/buttons/GenericButton';
|
import GenericButton from '@/components/buttons/GenericButton';
|
||||||
import { useToast } from '@/hooks/useToast';
|
import { useToast } from '@/hooks/useToast';
|
||||||
@ -54,7 +53,7 @@ const UserRelaysTable = ({ ndk, userRelays, setUserRelays, reInitializeNDK }) =>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const header = (
|
const tableHeader = (
|
||||||
<div className="text-[#f8f8ff]">
|
<div className="text-[#f8f8ff]">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
@ -62,9 +61,10 @@ const UserRelaysTable = ({ ndk, userRelays, setUserRelays, reInitializeNDK }) =>
|
|||||||
</div>
|
</div>
|
||||||
<GenericButton
|
<GenericButton
|
||||||
outlined
|
outlined
|
||||||
icon="pi pi-plus"
|
icon={collapsed ? 'pi pi-plus' : 'pi pi-minus'}
|
||||||
label="Add Relay"
|
label={collapsed ? 'Add Relay' : 'Hide'}
|
||||||
severity="success"
|
severity="success"
|
||||||
|
className="w-fit"
|
||||||
onClick={() => setCollapsed(!collapsed)}
|
onClick={() => setCollapsed(!collapsed)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -77,12 +77,14 @@ const UserRelaysTable = ({ ndk, userRelays, setUserRelays, reInitializeNDK }) =>
|
|||||||
onChange={e => setNewRelayUrl(e.target.value)}
|
onChange={e => setNewRelayUrl(e.target.value)}
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
/>
|
/>
|
||||||
<GenericButton label="+" severity="success" outlined onClick={addRelay} />
|
<GenericButton className="w-fit" label="+" severity="success" outlined onClick={addRelay} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const relayUrlBody = rowData => rowData;
|
||||||
|
|
||||||
const relayStatusBody = url => {
|
const relayStatusBody = url => {
|
||||||
const isConnected = relayStatuses[url];
|
const isConnected = relayStatuses[url];
|
||||||
return (
|
return (
|
||||||
@ -118,13 +120,23 @@ const UserRelaysTable = ({ ndk, userRelays, setUserRelays, reInitializeNDK }) =>
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Define columns for GenericDataTable
|
||||||
|
const columns = [
|
||||||
|
{ field: 'url', header: 'Relay URL', body: relayUrlBody },
|
||||||
|
{ header: 'Status', body: relayStatusBody },
|
||||||
|
{ header: 'Actions', body: relayActionsBody },
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<DataTable value={userRelays} className="border-none" header={header}>
|
<GenericDataTable
|
||||||
<Column field={url => url} header="Relay URL"></Column>
|
value={userRelays}
|
||||||
<Column body={relayStatusBody} header="Status"></Column>
|
columns={columns}
|
||||||
<Column body={relayActionsBody} header="Actions"></Column>
|
className="border-none"
|
||||||
</DataTable>
|
header={tableHeader}
|
||||||
|
dataKey={rowData => rowData}
|
||||||
|
>
|
||||||
|
</GenericDataTable>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
114
src/components/ui/DataTables/DataTable.js
Normal file
114
src/components/ui/DataTables/DataTable.js
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { DataTable } from 'primereact/datatable';
|
||||||
|
import { Column } from 'primereact/column';
|
||||||
|
|
||||||
|
const GenericDataTable = ({
|
||||||
|
value,
|
||||||
|
columns,
|
||||||
|
header,
|
||||||
|
emptyMessage = "No records found.",
|
||||||
|
className,
|
||||||
|
style,
|
||||||
|
pt,
|
||||||
|
stripedRows = false,
|
||||||
|
dataKey,
|
||||||
|
// Pagination props
|
||||||
|
paginator = false,
|
||||||
|
rows,
|
||||||
|
rowsPerPageOptions,
|
||||||
|
paginatorTemplate = "FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown",
|
||||||
|
currentPageReportTemplate = "Showing {first} to {last} of {totalRecords} entries",
|
||||||
|
// Sorting props
|
||||||
|
sortMode,
|
||||||
|
sortField,
|
||||||
|
sortOrder,
|
||||||
|
onSort,
|
||||||
|
// Filtering props
|
||||||
|
filters,
|
||||||
|
onFilter,
|
||||||
|
globalFilter,
|
||||||
|
globalFilterFields,
|
||||||
|
filterDisplay = "menu",
|
||||||
|
// Selection props
|
||||||
|
selection,
|
||||||
|
onSelectionChange,
|
||||||
|
selectionMode,
|
||||||
|
// Row expansion
|
||||||
|
rowExpansionTemplate,
|
||||||
|
expandedRows,
|
||||||
|
onRowToggle,
|
||||||
|
// Context Menu
|
||||||
|
onContextMenu,
|
||||||
|
contextMenuSelection,
|
||||||
|
onContextMenuSelectionChange,
|
||||||
|
// Other common props
|
||||||
|
loading = false,
|
||||||
|
...rest // Allows passing any other DataTable props
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<DataTable
|
||||||
|
value={value}
|
||||||
|
header={header}
|
||||||
|
emptyMessage={emptyMessage}
|
||||||
|
className={className}
|
||||||
|
style={style}
|
||||||
|
pt={pt}
|
||||||
|
stripedRows={stripedRows}
|
||||||
|
dataKey={dataKey}
|
||||||
|
// Pagination
|
||||||
|
paginator={paginator}
|
||||||
|
rows={rows}
|
||||||
|
rowsPerPageOptions={rowsPerPageOptions}
|
||||||
|
paginatorTemplate={paginatorTemplate}
|
||||||
|
currentPageReportTemplate={currentPageReportTemplate}
|
||||||
|
// Sorting
|
||||||
|
sortMode={sortMode}
|
||||||
|
sortField={sortField}
|
||||||
|
sortOrder={sortOrder}
|
||||||
|
onSort={onSort}
|
||||||
|
// Filtering
|
||||||
|
filters={filters}
|
||||||
|
onFilter={onFilter}
|
||||||
|
globalFilter={globalFilter}
|
||||||
|
globalFilterFields={globalFilterFields}
|
||||||
|
filterDisplay={filterDisplay}
|
||||||
|
// Selection
|
||||||
|
selection={selection}
|
||||||
|
onSelectionChange={onSelectionChange}
|
||||||
|
selectionMode={selectionMode}
|
||||||
|
// Row Expansion
|
||||||
|
rowExpansionTemplate={rowExpansionTemplate}
|
||||||
|
expandedRows={expandedRows}
|
||||||
|
onRowToggle={onRowToggle}
|
||||||
|
// Context Menu
|
||||||
|
onContextMenu={onContextMenu}
|
||||||
|
contextMenuSelection={contextMenuSelection}
|
||||||
|
onContextMenuSelectionChange={onContextMenuSelectionChange}
|
||||||
|
// Loading state
|
||||||
|
loading={loading}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{columns && columns.map((col, i) => (
|
||||||
|
<Column
|
||||||
|
key={col.field || i}
|
||||||
|
field={col.field}
|
||||||
|
header={col.header}
|
||||||
|
body={col.body}
|
||||||
|
sortable={col.sortable}
|
||||||
|
filter={col.filter}
|
||||||
|
filterPlaceholder={col.filterPlaceholder}
|
||||||
|
filterElement={col.filterElement}
|
||||||
|
selectionMode={col.selectionMode}
|
||||||
|
headerStyle={col.headerStyle}
|
||||||
|
bodyStyle={col.bodyStyle}
|
||||||
|
style={col.style}
|
||||||
|
expander={col.expander}
|
||||||
|
// Add other column props as needed
|
||||||
|
{...col.columnProps} // Allows passing any other Column props
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</DataTable>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GenericDataTable;
|
@ -11,7 +11,8 @@ const appConfig = {
|
|||||||
],
|
],
|
||||||
authorPubkeys: [
|
authorPubkeys: [
|
||||||
'f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741',
|
'f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741',
|
||||||
'c67cd3e1a83daa56cff16f635db2fdb9ed9619300298d4701a58e68e84098345'
|
'c67cd3e1a83daa56cff16f635db2fdb9ed9619300298d4701a58e68e84098345',
|
||||||
|
'6260f29fa75c91aaa292f082e5e87b438d2ab4fdf96af398567b01802ee2fcd4'
|
||||||
],
|
],
|
||||||
customLightningAddresses: [
|
customLightningAddresses: [
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user