A

ayopelumi2014

@ayopelumi2014

free
Joined 8 months ago
Snippets

8

Collections

3

Saved

2

Stars

0

Forks

2

Followers

0

Public Snippets

2 forks0 stars

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>
    );
}
shadcnreactnextjs
javascript

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()}`);
});
Nodejsexpressjs
javascript

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;
Nodejs

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");
nodejspostgress

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,
  };
};
reactusenew hook+1
javascript

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>
  );
}