@ayopelumi2014
free@ayopelumi2014
free8
3
2
0
2
0
A description to show notification display
"use client";
import { Bell } from "lucide-react";
import { useEffect, useState, useMemo, useCallback } from "react";
import { Button } from "../../ui/button";
import { Popover, PopoverContent, PopoverTrigger } from "../../ui/popover";
import { ScrollArea } from "../../ui/scroll-area";
import { Skeleton } from "../../ui/skeleton";
import { debounce } from "lodash";
import {
getUnreadNotifications,
markAllNotificationsAsRead,
} from "@/lib/actions/notifications.actions";
import { NotificationType } from "@/types/notifications";
import React from "react";
import { createSupabaseClient } from "@/lib/supabase";
import { NotificationItem } from "./notificationItem";
import { markNotificationAsReadClient } from "@/lib/actions/notifications.client";
import { useRouter } from "next/navigation";
import { toCamelCase } from "@/lib/helpers";
const supabase = createSupabaseClient()
export function Notifications({ userId, data }: { userId: string, data: NotificationType[] }) {
const router = useRouter();
const [notifications, setNotifications] = useState < NotificationType[] > (data);
const [loading, setLoading] = useState(false);
const [isOpen, setIsOpen] = useState(false);
//console.log({ notifications }, { data })
const unreadNotifications = useMemo(
() => notifications.filter((n) => !n.isRead),
[notifications]
);
const unreadCount = unreadNotifications.length;
const loadNotifications = async () => {
const { data } = await getUnreadNotifications(userId, { limit: 50 });
// setNotifications(data);
}
useEffect(() => {
// try {
// loadNotifications()
// }
// catch (error) {
// //console.log({ error })
// }
// finally {
// setLoading(false)
// }
// getUnreadNotifications(userId, { limit: 40 }).then(setNotifications).catch(console.error);
const handleNewNotification = debounce((payload: any) => {
//console.log({ payload })
setNotifications(prev => [{
...payload.new,
snippetId: payload.new.snippet_id,
collectionId: payload.new.collection_id,
metadata: payload.new.metadata,
sender: toCamelCase(payload.new.metadata.sender),
createdAt: new Date(payload.new.created_at).toISOString(),
isRead: !!payload.new.read_at,
actor: payload.new.sender,
snippet: payload.new.snippet,
collection: payload.new.collection,
actionType: payload.new.type
}, ...prev]);
}, 300);
const channel = supabase
.channel(`realtime-notifications-${userId}`)
.on(
"postgres_changes",
{
event: "INSERT",
schema: "public",
table: "notifications",
filter: `user_id=eq.${userId}`,
},
handleNewNotification
)
.subscribe();
return () => {
supabase.removeChannel(channel);
handleNewNotification.cancel();
};
}, []);
const handleMarkAsRead = async (id: string) => {
try {
await markNotificationAsReadClient(id);
setNotifications(prev =>
prev.map(n => n.id === id ? { ...n, isRead: true } : n)
);
} catch (error) {
//console.log({ error })
console.error("Failed to mark notification as read:", error);
}
};
const handleMarkAllAsRead = async () => {
try {
await markAllNotificationsAsRead(userId);
setNotifications(prev =>
prev.map(n => ({ ...n, isRead: true }))
);
} catch (error) {
console.error("Failed to mark all notifications as read:", error);
}
};
const handleNotificationClick = async (id: string) => {
const notification = notifications.find((n) => n.id === id);
if (!notification) return;
await handleMarkAsRead(id);
if (notification.snippetId) {
router.push(`/snippets/${notification.snippetId}`);
} else if (notification.collectionId) {
router.push(`/collections/${notification.collectionId}`);
}
};
return (
<Popover open={isOpen} onOpenChange={setIsOpen}>
<PopoverTrigger asChild>
<Button
variant="ghost"
size="icon"
className="relative rounded-full h-10 w-10"
aria-label="Notifications"
aria-haspopup="true"
aria-expanded={isOpen}
>
<Bell className="h-5 w-5" />
{unreadCount > 0 && (
<span className="absolute top-1 right-1 h-3 w-3 rounded-full bg-primary flex items-center justify-center text-[10px] text-white">
{unreadCount > 9 ? "9+" : unreadCount}
</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent
className="w-[360px] p-0 rounded-lg shadow-lg border bg-background"
align="end"
sideOffset={10}
>
<div className="p-4 border-b flex justify-between items-center">
<h3 className="font-semibold text-sm">Notifications</h3>
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="sm"
className="text-muted-foreground h-6 px-2"
onClick={handleMarkAllAsRead}
disabled={unreadCount === 0}
>
Mark all as read
</Button>
</div>
</div>
<ScrollArea className="h-[400px]">
{loading ? (
<div className="space-y-4 p-4">
{[...Array(5)].map((_, i) => (
<div key={i} className="flex items-start gap-3">
<Skeleton className="h-10 w-10 rounded-full animate-pulse" />
<div className="space-y-2 flex-1">
<Skeleton className="h-4 w-3/4 animate-pulse" />
<Skeleton className="h-3 w-1/2 animate-pulse" />
</div>
</div>
))}
</div>
) : notifications.length === 0 ? (
<div className="p-6 text-center">
<p className="text-sm text-muted-foreground">
No notifications yet
</p>
</div>
) : (
<div className="divide-y">
{notifications.map((notification) => (
<NotificationItem
key={notification.id}
notification={notification}
onClick={handleMarkAsRead}
/>
))}
</div>
)}
</ScrollArea>
{notifications.length > 0 && (
<div className="p-2 border-t text-center">
<Button
variant="ghost"
size="sm"
className="text-muted-foreground"
// onClick={() => router.push("/notifications")}
>
View all notifications
</Button>
</div>
)}
</PopoverContent>
</Popover>
);
}ProvisionStore API implementation
const express = require('express');
const ProvisionStore = require('./ProvisionStore');
const app = express();
app.use(express.json());
// Create an instance of ProvisionStore
const myStore = new ProvisionStore("API Grocery", "Online");
// GET all products
app.get('/products', (req, res) => {
res.json(myStore.getProducts());
});
// GET product by ID
app.get('/products/:id', (req, res) => {
const product = myStore.getProductById(parseInt(req.params.id));
if (!product) {
return res.status(404).json({ error: 'Product not found' });
}
res.json(product);
});
// POST new product
app.post('/products', (req, res) => {
try {
const { productName, cost, stockStatus } = req.body;
const newProduct = myStore.addProduct(productName, cost, stockStatus);
res.status(201).json(newProduct);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// PATCH update product (except stock status)
app.patch('/products/:id', (req, res) => {
try {
const updatedProduct = myStore.updateProduct(parseInt(req.params.id), req.body);
res.json(updatedProduct);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// PATCH update only stock status
app.patch('/products/:id/status/:status', (req, res) => {
try {
const updatedProduct = myStore.updateStockStatus(
parseInt(req.params.id),
req.params.status
);
res.json(updatedProduct);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// DELETE product
app.delete('/products/:id', (req, res) => {
try {
const deletedProduct = myStore.deleteProduct(parseInt(req.params.id));
res.json(deletedProduct);
} catch (error) {
res.status(404).json({ error: error.message });
}
});
const PORT = 3010;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
console.log(`Store Name: ${myStore.getShopName()}`);
console.log(`Location: ${myStore.getLocation()}`);
});ProvisionStore Class Implementation
class ProvisionStore {
#shopName;
#location;
products;
constructor(shopName, location) {
this.#shopName = shopName;
this.#location = location;
this.products = [];
}
// Get shop name
getShopName() {
return this.#shopName;
}
// Get location
getLocation() {
return this.#location;
}
// Method to get all products
getProducts() {
return this.products;
}
// Method to get a product by ID
getProductById(id) {
return this.products.find(product => product.id === id);
}
// Method to add a new product
addProduct(productName, cost, stockStatus) {
const validStatuses = ['in-stock', 'low-stock', 'out-of-stock'];
if (!validStatuses.includes(stockStatus)) {
throw new Error('Invalid stock status. Must be one of: in-stock, low-stock, out-of-stock');
}
const newProduct = {
id: Math.floor(Math.random() * 1000000),
productName,
cost,
stockStatus,
createdAt: new Date()
};
this.products.push(newProduct);
return newProduct;
}
// Method to edit product properties
updateProduct(id, updates) {
const product = this.getProductById(id);
if (!product) {
throw new Error('Product not found');
}
if (updates.stockStatus) {
throw new Error('Cannot update stock status with this method. Use updateStockStatus instead.');
}
Object.assign(product, updates);
return product;
}
// Method to update only the stock status
updateStockStatus(id, newStatus) {
const validStatuses = ['in-stock', 'low-stock', 'out-of-stock'];
if (!validStatuses.includes(newStatus)) {
throw new Error('Invalid stock status. Must be one of: in-stock, low-stock, out-of-stock');
}
const product = this.getProductById(id);
if (!product) {
throw new Error('Product not found');
}
product.stockStatus = newStatus;
return product;
}
// Method to delete a product by ID
deleteProduct(id) {
const index = this.products.findIndex(product => product.id === id);
if (index === -1) {
throw new Error('Product not found');
}
return this.products.splice(index, 1)[0];
}
}
module.exports = ProvisionStore;Php plugin snippet header
<? php
/*
Plugin Name: My Custom Plugin
Description: Adds custom functionality to my site.
Version: 1.0
Author: Your Name
*/It is for an assignment
console.log("the assignment is about the node js");Integrating the react use
import { useState, useCallback } from 'react';
import { useRouter } from 'next/navigation';
import { toast } from 'sonner';
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs';
import { createSnippet, getSnippetById, updateSnippet } from '@/lib/actions/snippets.actions';
import type { Snippet } from '@/types/snippets';
export const useSnippetEditor = (id?: string) => {
const isNew = id === 'new' || !id;
const router = useRouter();
const supabase = createClientComponentClient();
const [state, setState] = useState({
title: '',
description: '',
code: '',
tags: '',
isPremium: false,
isLoading: !isNew,
isRunning: false,
});
const fetchSnippet = useCallback(async () => {
if (isNew || !id) return;
try {
setState(prev => ({ ...prev, isLoading: true }));
const { snippet } = await getSnippetById(id);
if (snippet) {
setState({
title: snippet.title || '',
description: snippet.description || '',
code: snippet.code || '',
tags: snippet.tags?.join(', ') || '',
isPremium: snippet.isPremium || false,
isLoading: false,
isRunning: false,
});
return snippet;
}
} catch (error) {
console.error('Failed to fetch snippet:', error);
toast.error('Failed to load snippet');
} finally {
setState(prev => ({ ...prev, isLoading: false }));
}
}, [id, isNew]);
const handleSave = useCallback(
async (editorCode: string, language: string) => {
try {
setState(prev => ({ ...prev, isRunning: true }));
const { title, description, tags, isPremium } = state;
if (!title.trim()) {
toast.error('Title is required');
return;
}
if (!description.trim()) {
toast.error('Description is required');
return;
}
if (!editorCode.trim()) {
toast.error('Code is required');
return;
}
const { data: { session } } = await supabase.auth.getSession();
if (!session) {
toast.error('You must be logged in to save snippets');
return;
}
const processedTags = tags
.split(',')
.map(tag => tag.trim())
.filter(tag => tag.length > 0);
const snippetData = {
title: title.trim(),
description: description.trim(),
code: editorCode,
language,
isPublic: !isPremium,
isPremium,
tags: processedTags,
status: 'ACTIVE' as const,
};
if (isNew) {
const { data, error } = await createSnippet(snippetData);
if (error || !data) throw error || new Error('No data returned');
toast.success('Snippet created successfully');
router.push(`/dashboard/snippets/${data[0].id}`);
} else {
const { snippet, error } = await updateSnippet(id, snippetData);
if (error) throw error;
toast.success('Snippet updated successfully');
router.push(`/dashboard/snippets/${snippet.id}`);
}
} catch (error) {
console.error('Save error:', error);
toast.error('Failed to save snippet');
} finally {
setState(prev => ({ ...prev, isRunning: false }));
}
},
[state, id, isNew, router, supabase.auth]
);
const handleChange = useCallback((field: keyof typeof state, value: any) => {
setState(prev => ({ ...prev, [field]: value }));
}, []);
return {
...state,
isNew,
fetchSnippet,
handleSave,
handleChange,
};
};A simple React counter component
A simple React counter component
function TestComponent() {
const [count, setCount] = React.useState(0);
return (
<div>
<h2>Counter: {count}</h2>
<button onClick={() => setCount(c => c + 1)}>
Increment
</button>
<p>Click the button to test interactivity</p>
</div>
);
}