diff --git a/app/components/Contact.js b/app/components/Contact.js new file mode 100644 index 0000000..7426d07 --- /dev/null +++ b/app/components/Contact.js @@ -0,0 +1,74 @@ +"use client"; +import PropTypes from "prop-types"; +import Link from "next/link"; +import Image from "next/image"; +import EditButtons from "./EditButtons"; + +export function ErrorState({ title, message }) { + return ( +
+

{title}

+

{message}

+ + Back to Contacts + +
+ ); +} + +ErrorState.propTypes = { + title: PropTypes.string.isRequired, + message: PropTypes.string.isRequired, +}; + +function Contact({ contact, onDelete }) { + return ( +
  • +
    + {`${contact.name}'s { + e.target.src = "https://cdn-icons-png.flaticon.com/512/8847/8847419.png"; + }} + /> +
    +
    + + {contact.name} + +
    +
    + {contact.email} +
    +
    + {contact.phone_number || "—"} +
    +
    + +
    +
  • + ); +} + +Contact.propTypes = { + contact: PropTypes.shape({ + id: PropTypes.number, + name: PropTypes.string, + email: PropTypes.string, + phone_number: PropTypes.string, + image_url: PropTypes.string, + }), + onDelete: PropTypes.func, +}; + +Contact.defaultProps = { + contact: {}, + onDelete: () => {}, +}; + +export default Contact; diff --git a/app/components/ContactForm.js b/app/components/ContactForm.js new file mode 100644 index 0000000..1d6f309 --- /dev/null +++ b/app/components/ContactForm.js @@ -0,0 +1,151 @@ +"use client"; +import PropTypes from "prop-types"; +import { useState } from "react"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { ContactAPI } from "../data/contactAPI"; + +function ContactForm({ + contact = { id: 0, name: "", email: "", phone_number: "", image_url: "" }, +}) { + const router = useRouter(); + const [formData, setFormData] = useState({ + name: contact.name || "", + email: contact.email || "", + phone_number: contact.phone_number || "", + image_url: contact.image_url || "" + }); + const [errors, setErrors] = useState({}); + + const handleChange = (e) => { + const { name, value } = e.target; + setFormData(prev => ({ + ...prev, + [name]: value + })); + // Clear error when user starts typing + if (errors[name]) { + setErrors(prev => ({ ...prev, [name]: "" })); + } + }; + + const validateForm = () => { + const newErrors = {}; + if (!formData.name.trim()) newErrors.name = "Name is required"; + if (!formData.email.trim()) newErrors.email = "Email is required"; + if (!formData.phone_number.trim()) newErrors.phone_number = "Phone number is required"; + return newErrors; + }; + + const handleSubmit = (e) => { + e.preventDefault(); + const validationErrors = validateForm(); + setErrors(validationErrors); + + if (Object.keys(validationErrors).length === 0) { + try { + if (contact.id) { + ContactAPI.editContact({ + id: contact.id, + ...formData + }); + } else { + ContactAPI.addContact({ + id: Math.round(Math.random() * 100000000), + ...formData + }); + } + router.push("/contacts"); + } catch (error) { + console.error('Failed to save contact:', error); + setErrors({ submit: error.message }); + } + } + }; + + return ( +
    +
    + + + {errors.name &&
    {errors.name}
    } +
    + +
    + + + {errors.email &&
    {errors.email}
    } +
    + +
    + + + {errors.phone_number &&
    {errors.phone_number}
    } +
    + +
    + + +
    + + {errors.submit && ( +
    + {errors.submit} +
    + )} + +
    + + + Cancel + +
    +
    + ); +} + +ContactForm.propTypes = { + contact: PropTypes.shape({ + id: PropTypes.number, + name: PropTypes.string, + email: PropTypes.string, + phone_number: PropTypes.string, + image_url: PropTypes.string, + }), +}; + +export default ContactForm; diff --git a/app/components/EditButtons.js b/app/components/EditButtons.js new file mode 100644 index 0000000..d5287a1 --- /dev/null +++ b/app/components/EditButtons.js @@ -0,0 +1,83 @@ +"use client"; +import PropTypes from "prop-types"; +import { useState } from "react"; +import { RiEditLine, RiDeleteBinLine, RiCloseLine } from "react-icons/ri"; +import { useRouter } from "next/navigation"; + +function EditButtons({ contact, onDelete }) { + const [showConfirmation, setShowConfirmation] = useState(false); + const [deleteInProgress, setDeleteInProgress] = useState(false); + const router = useRouter(); + + const handleEdit = () => { + if (typeof contact.id === 'number') { + router.push(`/contacts/${contact.id}/edit`); + } else { + console.error('Invalid contact ID:', contact.id); + } + }; + + const handleDelete = async () => { + try { + setDeleteInProgress(true); + await onDelete(contact); + setShowConfirmation(false); + router.refresh(); + } catch (error) { + console.error('Failed to delete contact:', error); + } finally { + setDeleteInProgress(false); + } + }; + + return ( +
    + + + {!showConfirmation ? ( + + ) : ( +
    + + Delete {contact.name}? + + + +
    + )} +
    + ); +} + +EditButtons.propTypes = { + contact: PropTypes.shape({ + id: PropTypes.number.isRequired, + name: PropTypes.string.isRequired, + }).isRequired, + onDelete: PropTypes.func.isRequired, +}; + +export default EditButtons; \ No newline at end of file diff --git a/app/components/Search.js b/app/components/Search.js new file mode 100644 index 0000000..86c4f19 --- /dev/null +++ b/app/components/Search.js @@ -0,0 +1,47 @@ +"use client"; +import { useState } from "react"; +import { RiSearchLine } from "react-icons/ri"; +import PropTypes from "prop-types"; + +function Search({ onSearchTermChange, initialValue = "" }) { + const [term, setTerm] = useState(initialValue); + + const handleClear = () => { + setTerm(""); + onSearchTermChange(""); + }; + + return ( +
    + + + + { + setTerm(e.target.value); + onSearchTermChange(e.target.value); + }} + /> + {term && ( + + )} +
    + ); +} + +Search.propTypes = { + onSearchTermChange: PropTypes.func.isRequired, + initialValue: PropTypes.string, +}; + +export default Search; diff --git a/app/contacts/[id]/edit/page.js b/app/contacts/[id]/edit/page.js new file mode 100644 index 0000000..2564410 --- /dev/null +++ b/app/contacts/[id]/edit/page.js @@ -0,0 +1,39 @@ +"use client"; +import ContactForm from "@/app/components/ContactForm"; +import Link from "next/link"; +import { ContactAPI } from "@/app/data/contactAPI"; +import { ErrorState } from "@/app/components/Contact"; + +function EditContact({ params }) { + const id = Number(params.id); + + // Handle invalid ID + if (isNaN(id)) { + return ; + } + + const contact = ContactAPI.get(id); + + // Handle contact not found + if (!contact) { + return ; + } + + return ( +
    +

    Edit Contact

    + + + Back to Contacts + +
    + ); +} + +export default EditContact; diff --git a/app/contacts/[id]/page.js b/app/contacts/[id]/page.js new file mode 100644 index 0000000..87782e1 --- /dev/null +++ b/app/contacts/[id]/page.js @@ -0,0 +1,63 @@ +"use client"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import Image from "next/image"; +import EditButtons from "@/app/components/EditButtons"; +import { ContactAPI } from "@/app/data/contactAPI"; +import { ErrorState } from "@/app/components/Contact"; + +function ContactPage({ params }) { + const router = useRouter(); + const id = Number(params.id); + + // Handle invalid ID + if (isNaN(id)) { + return ; + } + + const contact = ContactAPI.get(id); + + // Handle contact not found + if (!contact) { + return ; + } + + const handleDelete = () => { + ContactAPI.remove(contact); + router.push("/contacts"); + }; + + return ( +
    +
    +

    {contact.name}

    + {`${contact.name}'s { + e.target.src = "https://cdn-icons-png.flaticon.com/512/8847/8847419.png"; + }} + /> +
    +

    Email: {contact.email}

    +

    Phone: {contact.phone_number}

    +
    + + + Back to Contacts + +
    +
    + ); +} + +export default ContactPage; diff --git a/app/contacts/new/page.js b/app/contacts/new/page.js new file mode 100644 index 0000000..b77ba3f --- /dev/null +++ b/app/contacts/new/page.js @@ -0,0 +1,16 @@ +"use client"; +import Link from "next/link"; +import ContactForm from "../../components/ContactForm"; +import { ContactAPI } from "@/app/data/contactAPI"; + +function NewContact() { + return ( +
    +

    Add Contact

    + + Home +
    + ); +} + +export default NewContact; diff --git a/app/contacts/page.js b/app/contacts/page.js new file mode 100644 index 0000000..c8ab946 --- /dev/null +++ b/app/contacts/page.js @@ -0,0 +1,75 @@ +"use client"; +import Link from "next/link"; +import Contact from "../components/Contact"; +import { ContactAPI } from "../data/contactAPI"; +import { useState } from "react"; +import Search from "../components/Search"; + +function ContactsPage() { + const [contacts, setContacts] = useState(ContactAPI.contacts); + const [searchTerm, setSearchTerm] = useState(""); + + const handleSearch = (term) => { + setSearchTerm(term); + const filtered = ContactAPI.contacts.filter((contact) => + contact.name.toLowerCase().includes(term.toLowerCase()) + ); + setContacts(filtered); + }; + + const handleDelete = (contact) => { + const updatedContacts = ContactAPI.remove(contact); + setContacts(updatedContacts); + }; + + return ( +
    +
    +

    Contacts

    + + Add New Contact + +
    + +
    + +

    + {contacts.length} contact{contacts.length !== 1 ? 's' : ''} found + {searchTerm && ` for "${searchTerm}"`} +

    +
    + + {contacts.length > 0 ? ( +
    +
    +
    Profile
    +
    Name
    +
    Email
    +
    Phone
    +
    +
    +
      + {contacts.map(contact => ( +
    • + +
    • + ))} +
    +
    + ) : ( +
    +

    + {searchTerm + ? "No contacts found matching your search." + : "No contacts yet. Add your first contact!"} +

    +
    + )} +
    + ); +} + +export default ContactsPage; diff --git a/app/data/contactAPI.js b/app/data/contactAPI.js new file mode 100644 index 0000000..d07db2e --- /dev/null +++ b/app/data/contactAPI.js @@ -0,0 +1,86 @@ +const DEFAULT_PROFILE_IMAGE = 'https://cdn-icons-png.flaticon.com/512/8847/8847419.png'; + +const ContactAPI = { + contacts: [], + + all() { + return [...this.contacts]; + }, + + get(id) { + if (typeof id !== 'number') { + throw new Error('ID must be a number'); + } + return this.contacts.find(contact => contact.id === id); + }, + + addContact({ id, name, email, phone_number, image_url }) { + if (!id || !name || !email) { + throw new Error('ID, name, and email are required'); + } + + if (this.contacts.some(contact => contact.id === id)) { + throw new Error('Contact with this ID already exists'); + } + + const newContact = { + id, + name, + email, + phone_number: phone_number || '', + image_url: image_url || DEFAULT_PROFILE_IMAGE + }; + + this.contacts.push(newContact); + return newContact; + }, + + remove(contact) { + if (!contact || !contact.id) { + throw new Error('Valid contact with ID is required'); + } + + const index = this.contacts.findIndex(c => c.id === contact.id); + if (index === -1) { + throw new Error('Contact not found'); + } + + this.contacts.splice(index, 1); + return [...this.contacts]; + }, + + editContact({ id, name, email, phone_number, image_url }) { + if (!id || !name || !email) { + throw new Error('ID, name, and email are required'); + } + + const index = this.contacts.findIndex(c => c.id === id); + if (index === -1) { + throw new Error('Contact not found'); + } + + const updatedContact = { + ...this.contacts[index], + name, + email, + phone_number: phone_number || this.contacts[index].phone_number, + image_url: image_url || this.contacts[index].image_url || DEFAULT_PROFILE_IMAGE + }; + + this.contacts[index] = updatedContact; + return updatedContact; + }, + + search(query) { + if (!query) return [...this.contacts]; + + const searchTerm = query.toLowerCase(); + return this.contacts.filter(contact => + contact.name.toLowerCase().includes(searchTerm) || + contact.email.toLowerCase().includes(searchTerm) || + contact.phone_number?.toLowerCase().includes(searchTerm) + ); + } +}; + +export { ContactAPI }; diff --git a/app/globals.css b/app/globals.css index d4f491e..a1fa68f 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,76 +1,20 @@ :root { --max-width: 1100px; - --border-radius: 12px; - --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono', - 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro', - 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace; - - --foreground-rgb: 0, 0, 0; - --background-start-rgb: 214, 219, 220; - --background-end-rgb: 255, 255, 255; - - --primary-glow: conic-gradient( - from 180deg at 50% 50%, - #16abff33 0deg, - #0885ff33 55deg, - #54d6ff33 120deg, - #0071ff33 160deg, - transparent 360deg - ); - --secondary-glow: radial-gradient( - rgba(255, 255, 255, 1), - rgba(255, 255, 255, 0) - ); - - --tile-start-rgb: 239, 245, 249; - --tile-end-rgb: 228, 232, 233; - --tile-border: conic-gradient( - #00000080, - #00000040, - #00000030, - #00000020, - #00000010, - #00000010, - #00000080 - ); - - --callout-rgb: 238, 240, 241; - --callout-border-rgb: 172, 175, 176; - --card-rgb: 180, 185, 188; - --card-border-rgb: 131, 134, 135; -} + --border-radius: 8px; + --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; -@media (prefers-color-scheme: dark) { - :root { - --foreground-rgb: 255, 255, 255; - --background-start-rgb: 0, 0, 0; - --background-end-rgb: 0, 0, 0; - - --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0)); - --secondary-glow: linear-gradient( - to bottom right, - rgba(1, 65, 255, 0), - rgba(1, 65, 255, 0), - rgba(1, 65, 255, 0.3) - ); - - --tile-start-rgb: 2, 13, 46; - --tile-end-rgb: 2, 5, 19; - --tile-border: conic-gradient( - #ffffff80, - #ffffff40, - #ffffff30, - #ffffff20, - #ffffff10, - #ffffff10, - #ffffff80 - ); - - --callout-rgb: 20, 20, 20; - --callout-border-rgb: 108, 108, 108; - --card-rgb: 100, 100, 100; - --card-border-rgb: 200, 200, 200; - } + /* Modern color palette */ + --primary-color: #2563eb; + --primary-hover: #1d4ed8; + --primary-dark: #1a1d23; + --secondary-color: #64748b; + --background-color: #f8fafc; + --surface-color: #ffffff; + --text-primary: #1e293b; + --text-secondary: #64748b; + --error-color: #ef4444; + --success-color: #22c55e; + --border-color: #e2e8f0; } * { @@ -83,25 +27,414 @@ html, body { max-width: 100vw; overflow-x: hidden; + font-family: var(--font-sans); + background-color: var(--background-color); + color: var(--text-primary); } -body { - color: rgb(var(--foreground-rgb)); - background: linear-gradient( - to bottom, - transparent, - rgb(var(--background-end-rgb)) - ) - rgb(var(--background-start-rgb)); +a { + color: var(--primary-color); + text-decoration: none; + transition: color 0.2s ease; } -a { - color: inherit; +a:hover { + color: var(--primary-hover); +} + +/* Contacts List Styling */ +.contacts-list { + background: var(--surface-color); + border-radius: 8px; + overflow: hidden; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.list-header { + display: grid; + grid-template-columns: 80px 2fr 2fr 1fr 100px; + gap: 1rem; + padding: 1rem; + background: var(--background-alt); + border-bottom: 1px solid var(--border-color); +} + +.list-header div { + font-weight: 500; + color: var(--text-secondary); + font-size: 0.9rem; +} + +.contact-list { + list-style: none; + padding: 0; + margin: 0; +} + +.contact-list li { + border-bottom: 1px solid var(--border-color); +} + +.contact-list li:last-child { + border-bottom: none; +} + +.contact-list li:hover { + background: var(--hover-color); +} + +.contact-list a { + color: var(--primary-color); + font-weight: 500; + text-decoration: none; + transition: color 0.2s; +} + +.contact-list a:hover { + color: var(--primary-dark); + text-decoration: underline; +} + +.contact-list .contact-name { + font-size: 1rem; + line-height: 1.4; +} + +/* Contact Item Styling */ +.contact-item { + display: grid; + grid-template-columns: 80px 2fr 2fr 1fr 100px; + gap: 1rem; + align-items: center; + padding: 1rem; + transition: background-color 0.2s; + border-bottom: 1px solid var(--border-color); +} + +.contact-item:last-child { + border-bottom: none; +} + +.contact-item:hover { + background-color: var(--hover-color); +} + +.contact-profile { + display: flex; + align-items: center; +} + +.profile-image { + border-radius: 50%; + object-fit: cover; + background-color: var(--background-alt); +} + +.contact-name { + font-weight: 500; +} + +.name-link { + color: var(--primary-color); + text-decoration: none; + transition: color 0.2s; +} + +.name-link:hover { + color: var(--primary-dark); + text-decoration: underline; +} + +.contact-email { + color: var(--text-secondary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.contact-phone { + color: var(--text-secondary); + font-size: 0.9rem; +} + +.contact-actions { + display: flex; + gap: 0.5rem; + justify-content: flex-end; +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .contact-item { + grid-template-columns: 60px 1fr auto; + gap: 0.75rem; + } + + .contact-email, + .contact-phone { + display: none; + } +} + +/* Search Section */ +.search-section { + margin-bottom: 1.5rem; +} + +.results-count { + margin-top: 0.5rem; + color: var(--text-secondary); + font-size: 0.9rem; +} + +/* Empty State */ +.empty-state { + text-align: center; + padding: 3rem 1rem; + background: var(--surface-color); + border-radius: 8px; + color: var(--text-secondary); +} + +/* Header Section */ +.header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2rem; +} + +.primary-button { + background: var(--primary-color); + color: white; + padding: 0.5rem 1rem; + border-radius: 6px; text-decoration: none; + font-weight: 500; + transition: background 0.2s; +} + +.primary-button:hover { + background: var(--primary-dark); +} + +/* Form Styling */ +form { + max-width: 600px; + margin: 2rem auto; + padding: 2rem; + background-color: var(--surface-color); + border-radius: var(--border-radius); + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +} + +input, textarea { + width: 100%; + padding: 0.75rem; + margin: 0.5rem 0 1rem; + border: 1px solid var(--border-color); + border-radius: var(--border-radius); + font-size: 1rem; + transition: border-color 0.2s ease; +} + +input:focus, textarea:focus { + outline: none; + border-color: var(--primary-color); +} + +/* Button Styling */ +button { + padding: 0.5rem 1rem; + border: none; + border-radius: var(--border-radius); + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; +} + +.secondary-button { + background-color: var(--secondary-color); + color: white; +} + +.secondary-button:hover { + background-color: #4b5563; +} + +.delete-button { + background-color: var(--error-color); + color: white; +} + +.delete-button:hover { + background-color: #dc2626; +} + +.cancel-button { + padding: 0.5rem; + border: none; + background: none; + cursor: pointer; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + transition: background-color 0.2s; +} + +.cancel-button:hover { + background-color: rgba(220, 38, 38, 0.1); +} + +.cancel-button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* Error Messages */ +.error-message { + color: var(--error-color); + font-size: 0.875rem; + margin-top: -0.5rem; + margin-bottom: 1rem; +} + +/* Container */ +.container { + max-width: var(--max-width); + margin: 0 auto; + padding: 2rem; +} + +/* Page Header */ +.page-header { + margin-bottom: 2rem; + padding-bottom: 1rem; + border-bottom: 1px solid var(--border-color); +} + +.page-header h1 { + color: var(--text-primary); + font-size: 2rem; + font-weight: 600; +} + +/* Search Bar Styling */ +.search-container { + margin: 1rem 0; + width: 100%; +} + +.search-input-wrapper { + position: relative; + display: flex; + align-items: center; + background-color: var(--surface-color); + border: 1px solid var(--border-color); + border-radius: var(--border-radius); + padding: 0.5rem; + transition: all 0.2s ease; +} + +.search-input-wrapper:focus-within { + border-color: var(--primary-color); + box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.1); +} + +.search-icon { + color: var(--text-secondary); + margin-right: 0.5rem; +} + +.search-input { + flex: 1; + border: none; + background: none; + padding: 0.5rem; + font-size: 1rem; + color: var(--text-primary); + width: 100%; +} + +.search-input::placeholder { + color: var(--text-secondary); +} + +.search-input:focus { + outline: none; +} + +.clear-button { + background: none; + border: none; + padding: 0.25rem; + color: var(--text-secondary); + cursor: pointer; + border-radius: var(--border-radius); + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; +} + +.clear-button:hover { + background-color: var(--border-color); + color: var(--text-primary); } +/* Circle Image Styling */ +.circle { + border-radius: 50%; + object-fit: cover; + border: 2px solid var(--border-color); + transition: border-color 0.2s ease; +} + +.circle:hover { + border-color: var(--primary-color); +} + +/* Responsive Design */ +@media (max-width: 768px) { + .contact-grid { + grid-template-columns: auto 1fr; + gap: 1rem; + padding: 1rem; + } + + .contact-grid li:not(:first-child):not(:nth-child(2)) { + grid-column: 1 / -1; + padding-left: calc(50px + 1rem); + } + + .contact-grid li:last-child { + justify-self: start; + } +} + +@media (max-width: 768px) { + .contact-grid { + display: block; + } + + .grid-headers { + display: none; + } +} + +/* Dark Mode */ @media (prefers-color-scheme: dark) { - html { - color-scheme: dark; + :root { + --background-color: #1a1d23; + --surface-color: #2f343a; + --text-primary: #ffffff; + --text-secondary: #a1a1aa; + --primary-color: #4f46e5; + --primary-hover: #4338ca; + --secondary-color: #6b7280; + --error-color: #ef4444; + --success-color: #22c55e; + --border-color: #4b5563; } } diff --git a/app/page.js b/app/page.js index 5f905a8..0f36a5e 100644 --- a/app/page.js +++ b/app/page.js @@ -1,95 +1,12 @@ -import Image from 'next/image' -import styles from './page.module.css' +import ContactPage from "./contacts/page"; -export default function Home() { +function Home() { return ( -
    -
    -

    - Get started by editing  - app/page.js -

    - -
    - -
    - Next.js Logo -
    - - +
    +

    Welcome to Your Contact List

    +
    - ) + ); } + +export default Home; diff --git a/package-lock.json b/package-lock.json index f3ada0d..5c611ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,14 +7,18 @@ "": { "name": "parsity_contact_list_eval", "version": "0.1.0", + "license": "ISC", "dependencies": { - "next": "14.0.2", + "next": "^14.2.28", + "proptypes": "^1.1.0", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "react-icons": "^5.3.0" }, "devDependencies": { "eslint": "^8", - "eslint-config-next": "14.0.2" + "eslint-config-next": "14.0.2", + "jsdoc": "^4.0.3" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -26,11 +30,48 @@ "node": ">=0.10.0" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/runtime": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", - "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", "dev": true, + "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -38,6 +79,20 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/types": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -127,10 +182,24 @@ "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, + "node_modules/@jsdoc/salty": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.9.tgz", + "integrity": "sha512-yYxMVH7Dqw6nO0d5NIV8OQWnitU8k6vXH8NtgqAfIa/IUqRMxRv/NUJJ08VEKbAakwxlgBl5PJdrU0dMPStsnw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v12.0.0" + } + }, "node_modules/@next/env": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.0.2.tgz", - "integrity": "sha512-HAW1sljizEaduEOes/m84oUqeIDAUYBR1CDwu2tobNlNDFP3cSm9d6QsOsGeNlIppU1p/p1+bWbYCbvwjFiceA==" + "version": "14.2.28", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.28.tgz", + "integrity": "sha512-PAmWhJfJQlP+kxZwCjrVd9QnR5x0R3u0mTXTiZDgSd4h5LdXmjxCCWbN9kq6hkZBOax8Rm3xDW5HagWyJuT37g==", + "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { "version": "14.0.2", @@ -142,12 +211,13 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.2.tgz", - "integrity": "sha512-i+jQY0fOb8L5gvGvojWyZMfQoQtDVB2kYe7fufOEiST6sicvzI2W5/EXo4lX5bLUjapHKe+nFxuVv7BA+Pd7LQ==", + "version": "14.2.28", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.28.tgz", + "integrity": "sha512-kzGChl9setxYWpk3H6fTZXXPFFjg7urptLq5o5ZgYezCrqlemKttwMT5iFyx/p1e/JeglTwDFRtb923gTJ3R1w==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -157,12 +227,13 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.2.tgz", - "integrity": "sha512-zRCAO0d2hW6gBEa4wJaLn+gY8qtIqD3gYd9NjruuN98OCI6YyelmhWVVLlREjS7RYrm9OUQIp/iVJFeB6kP1hg==", + "version": "14.2.28", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.28.tgz", + "integrity": "sha512-z6FXYHDJlFOzVEOiiJ/4NG8aLCeayZdcRSMjPDysW297Up6r22xw6Ea9AOwQqbNsth8JNgIK8EkWz2IDwaLQcw==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -172,12 +243,13 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.2.tgz", - "integrity": "sha512-tSJmiaon8YaKsVhi7GgRizZoV0N1Sx5+i+hFTrCKKQN7s3tuqW0Rov+RYdPhAv/pJl4qiG+XfSX4eJXqpNg3dA==", + "version": "14.2.28", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.28.tgz", + "integrity": "sha512-9ARHLEQXhAilNJ7rgQX8xs9aH3yJSj888ssSjJLeldiZKR4D7N08MfMqljk77fAwZsWwsrp8ohHsMvurvv9liQ==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -187,12 +259,13 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.2.tgz", - "integrity": "sha512-dXJLMSEOwqJKcag1BeX1C+ekdPPJ9yXbWIt3nAadhbLx5CjACoB2NQj9Xcqu2tmdr5L6m34fR+fjGPs+ZVPLzA==", + "version": "14.2.28", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.28.tgz", + "integrity": "sha512-p6gvatI1nX41KCizEe6JkF0FS/cEEF0u23vKDpl+WhPe/fCTBeGkEBh7iW2cUM0rvquPVwPWdiUR6Ebr/kQWxQ==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -202,12 +275,13 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.2.tgz", - "integrity": "sha512-WC9KAPSowj6as76P3vf1J3mf2QTm3Wv3FBzQi7UJ+dxWjK3MhHVWsWUo24AnmHx9qDcEtHM58okgZkXVqeLB+Q==", + "version": "14.2.28", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.28.tgz", + "integrity": "sha512-nsiSnz2wO6GwMAX2o0iucONlVL7dNgKUqt/mDTATGO2NY59EO/ZKnKEr80BJFhuA5UC1KZOMblJHWZoqIJddpA==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -217,12 +291,13 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.2.tgz", - "integrity": "sha512-KSSAwvUcjtdZY4zJFa2f5VNJIwuEVnOSlqYqbQIawREJA+gUI6egeiRu290pXioQXnQHYYdXmnVNZ4M+VMB7KQ==", + "version": "14.2.28", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.28.tgz", + "integrity": "sha512-+IuGQKoI3abrXFqx7GtlvNOpeExUH1mTIqCrh1LGFf8DnlUcTmOOCApEnPJUSLrSbzOdsF2ho2KhnQoO0I1RDw==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -232,12 +307,13 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.2.tgz", - "integrity": "sha512-2/O0F1SqJ0bD3zqNuYge0ok7OEWCQwk55RPheDYD0va5ij7kYwrFkq5ycCRN0TLjLfxSF6xI5NM6nC5ux7svEQ==", + "version": "14.2.28", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.28.tgz", + "integrity": "sha512-l61WZ3nevt4BAnGksUVFKy2uJP5DPz2E0Ma/Oklvo3sGj9sw3q7vBWONFRgz+ICiHpW5mV+mBrkB3XEubMrKaA==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -247,12 +323,13 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.2.tgz", - "integrity": "sha512-vJI/x70Id0oN4Bq/R6byBqV1/NS5Dl31zC+lowO8SDu1fHmUxoAdILZR5X/sKbiJpuvKcCrwbYgJU8FF/Gh50Q==", + "version": "14.2.28", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.28.tgz", + "integrity": "sha512-+Kcp1T3jHZnJ9v9VTJ/yf1t/xmtFAc/Sge4v7mVc1z+NYfYzisi8kJ9AsY8itbgq+WgEwMtOpiLLJsUy2qnXZw==", "cpu": [ "ia32" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -262,12 +339,13 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.2.tgz", - "integrity": "sha512-Ut4LXIUvC5m8pHTe2j0vq/YDnTEyq6RSR9vHYPqnELrDapPhLNz9Od/L5Ow3J8RNDWpEnfCiQXuVdfjlNEJ7ug==", + "version": "14.2.28", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.28.tgz", + "integrity": "sha512-1gCmpvyhz7DkB1srRItJTnmR2UwQPAUXXIg9r0/56g3O8etGmwlX68skKXJOp9EejW3hhv7nSQUJ2raFiz4MoA==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -317,11 +395,19 @@ "integrity": "sha512-6i/8UoL0P5y4leBIGzvkZdS85RDMG9y1ihZzmTZQ5LdHUYmZ7pKFoj8X0236s3lusPs1Fa5HTQUpwI+UfTcmeA==", "dev": true }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, "node_modules/@swc/helpers": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", - "integrity": "sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", + "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", + "license": "Apache-2.0", "dependencies": { + "@swc/counter": "^0.1.3", "tslib": "^2.4.0" } }, @@ -331,6 +417,31 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" + }, "node_modules/@typescript-eslint/parser": { "version": "6.10.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.10.0.tgz", @@ -696,6 +807,13 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -707,12 +825,13 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -753,9 +872,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001561", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001561.tgz", - "integrity": "sha512-NTt0DNoKe958Q0BE0j0c1V9jbUzhBxHIEJy7asmGrpE0yG63KTV7PLHPnK2E1O9RsQrQ081I3NLuXGS6zht3cw==", + "version": "1.0.30001714", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001714.tgz", + "integrity": "sha512-mtgapdwDLSSBnCI3JokHM7oEQBLxiJKVRtg10AxM1AyeiKcM96f0Mkbqeq+1AbiCtvMcHRulAAEMu693JrSWqg==", "funding": [ { "type": "opencollective", @@ -769,7 +888,21 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" + }, + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">= 10" + } }, "node_modules/chalk": { "version": "4.1.2", @@ -817,10 +950,11 @@ "dev": true }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -942,6 +1076,19 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-abstract": { "version": "1.22.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", @@ -1547,10 +1694,11 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -1720,11 +1868,6 @@ "node": ">=10.13.0" } }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" - }, "node_modules/globals": { "version": "13.23.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", @@ -2113,6 +2256,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -2299,6 +2443,56 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, + "node_modules/jsdoc": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.4.tgz", + "integrity": "sha512-zeFezwyXeG4syyYHbvh1A967IAqq/67yXtXvuL5wnqCkFZe8I0vKfm+EO+YEvLguo6w9CDUbrAXVtJSHh2E8rw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@babel/parser": "^7.20.15", + "@jsdoc/salty": "^0.2.1", + "@types/markdown-it": "^14.1.1", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^14.1.0", + "markdown-it-anchor": "^8.6.7", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "underscore": "~1.13.2" + }, + "bin": { + "jsdoc": "jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsdoc/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -2353,6 +2547,16 @@ "json-buffer": "3.0.1" } }, + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.9" + } + }, "node_modules/language-subtag-registry": { "version": "0.3.22", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", @@ -2384,6 +2588,16 @@ "node": ">= 0.8.0" } }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2399,6 +2613,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -2428,6 +2649,55 @@ "node": ">=10" } }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", + "dev": true, + "license": "Unlicense", + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2438,12 +2708,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -2471,6 +2742,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2478,15 +2762,16 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -2501,17 +2786,18 @@ "dev": true }, "node_modules/next": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/next/-/next-14.0.2.tgz", - "integrity": "sha512-jsAU2CkYS40GaQYOiLl9m93RTv2DA/tTJ0NRlmZIBIL87YwQ/xR8k796z7IqgM3jydI8G25dXvyYMC9VDIevIg==", + "version": "14.2.28", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.28.tgz", + "integrity": "sha512-QLEIP/kYXynIxtcKB6vNjtWLVs3Y4Sb+EClTC/CSVzdLD1gIuItccpu/n1lhmduffI32iPGEK2cLLxxt28qgYA==", + "license": "MIT", "dependencies": { - "@next/env": "14.0.2", - "@swc/helpers": "0.5.2", + "@next/env": "14.2.28", + "@swc/helpers": "0.5.5", "busboy": "1.6.0", - "caniuse-lite": "^1.0.30001406", + "caniuse-lite": "^1.0.30001579", + "graceful-fs": "^4.2.11", "postcss": "8.4.31", - "styled-jsx": "5.1.1", - "watchpack": "2.4.0" + "styled-jsx": "5.1.1" }, "bin": { "next": "dist/bin/next" @@ -2520,18 +2806,19 @@ "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.0.2", - "@next/swc-darwin-x64": "14.0.2", - "@next/swc-linux-arm64-gnu": "14.0.2", - "@next/swc-linux-arm64-musl": "14.0.2", - "@next/swc-linux-x64-gnu": "14.0.2", - "@next/swc-linux-x64-musl": "14.0.2", - "@next/swc-win32-arm64-msvc": "14.0.2", - "@next/swc-win32-ia32-msvc": "14.0.2", - "@next/swc-win32-x64-msvc": "14.0.2" + "@next/swc-darwin-arm64": "14.2.28", + "@next/swc-darwin-x64": "14.2.28", + "@next/swc-linux-arm64-gnu": "14.2.28", + "@next/swc-linux-arm64-musl": "14.2.28", + "@next/swc-linux-x64-gnu": "14.2.28", + "@next/swc-linux-x64-musl": "14.2.28", + "@next/swc-win32-arm64-msvc": "14.2.28", + "@next/swc-win32-ia32-msvc": "14.2.28", + "@next/swc-win32-x64-msvc": "14.2.28" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.3.0" @@ -2540,6 +2827,9 @@ "@opentelemetry/api": { "optional": true }, + "@playwright/test": { + "optional": true + }, "sass": { "optional": true } @@ -2837,6 +3127,12 @@ "react-is": "^16.13.1" } }, + "node_modules/proptypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proptypes/-/proptypes-1.1.0.tgz", + "integrity": "sha512-589N0gHNvg6ocCDia1knxMwguEcZq1qyD8fVxFZEdZP0YKI+GTHyflsJH2mYE0YZbo1KNj5W8avdSR4iJDV+fw==", + "license": "BSD" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2846,6 +3142,16 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -2889,6 +3195,15 @@ "react": "^18.2.0" } }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -2938,6 +3253,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21" + } + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -3329,6 +3654,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -3361,9 +3687,10 @@ } }, "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" }, "node_modules/type-check": { "version": "0.4.0", @@ -3468,6 +3795,13 @@ "node": ">=14.17" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -3483,6 +3817,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/underscore": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", + "dev": true, + "license": "MIT" + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -3492,18 +3833,6 @@ "punycode": "^2.1.0" } }, - "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3601,6 +3930,13 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index 5f7406f..4ad47ca 100644 --- a/package.json +++ b/package.json @@ -9,12 +9,20 @@ "lint": "next lint" }, "dependencies": { + "next": "^14.2.28", + "proptypes": "^1.1.0", "react": "^18", "react-dom": "^18", - "next": "14.0.2" + "react-icons": "^5.3.0" }, "devDependencies": { "eslint": "^8", - "eslint-config-next": "14.0.2" - } + "eslint-config-next": "14.0.2", + "jsdoc": "^4.0.3" + }, + "description": "This project has been created by a student at Parsity, an online software engineering course. The work in this repository is wholly of the student based on a sample starter project that can be accessed by looking at the repository that this project forks.", + "main": "next.config.js", + "keywords": [], + "author": "", + "license": "ISC" }