diff --git a/components/form/Input.js b/components/form/Input.js index 9efa20a7921..2e6e3d99991 100644 --- a/components/form/Input.js +++ b/components/form/Input.js @@ -1,4 +1,5 @@ export default function Input({ + type = "text", name, value, placeholder, @@ -14,7 +15,7 @@ export default function Input({ )} - -

Documentation

-

- Here you should find everything you need from getting started with - creating your Profile to more advanced topics. We welcome - contributions, check out the  - - LinkFree Repo - -   and the  - - documentation source - {" "} - on GitHub for more information. -

-
-
+ + +
+ +
{children} diff --git a/components/navbar/SideNav.js b/components/navbar/SideNav.js new file mode 100644 index 00000000000..3f20d4be999 --- /dev/null +++ b/components/navbar/SideNav.js @@ -0,0 +1,75 @@ +import { Disclosure } from "@headlessui/react"; +import { ChevronRightIcon } from "@heroicons/react/20/solid"; + +function classNames(...classes) { + return classes.filter(Boolean).join(" "); +} + +export default function SideNav({ navigation }) { + return ( +
    + {navigation.map((item) => ( +
  • + {!item.children ? ( + + + ) : ( + + {({ open }) => ( + <> + + + + {item.children.map((subItem) => ( +
  • + {/* 44px */} + + {subItem.name} + +
  • + ))} + + + )} + + )} + + ))} +
+ ); +} diff --git a/models/Profile.js b/models/Profile.js index 919d9b821a2..8b4fec2d880 100644 --- a/models/Profile.js +++ b/models/Profile.js @@ -1,6 +1,9 @@ -import mongoose from "mongoose"; +import mongoose, { Schema } from "mongoose"; -const ProfileSchema = new mongoose.Schema( +import { MilestoneSchema } from "./Profile/Milestone"; +import { EventSchema } from "./Profile/Event"; + +const ProfileSchema = new Schema( { source: { type: String, @@ -55,42 +58,12 @@ const ProfileSchema = new mongoose.Schema( }, links: { default: [], - type: [{ type: mongoose.Schema.Types.ObjectId, ref: "Link" }], + type: [{ type: Schema.Types.ObjectId, ref: "Link" }], + }, + milestones: { + type: [MilestoneSchema], + default: [], }, - milestones: [ - { - url: { - type: String, - required: true, - min: 2, - max: 256, - }, - date: { - type: Date, - required: true, - }, - isGoal: Boolean, - title: { - type: String, - required: true, - min: 2, - max: 256, - }, - icon: { - type: String, - required: true, - min: 2, - max: 32, - }, - description: { - type: String, - required: true, - min: 2, - max: 512, - }, - order: Number, - }, - ], testimonials: [ { username: { @@ -119,44 +92,10 @@ const ProfileSchema = new mongoose.Schema( isPinned: Boolean, }, ], - events: [ - { - isVirtual: Boolean, - color: String, - name: { - type: String, - required: true, - min: 2, - max: 256, - }, - description: { - type: String, - required: true, - min: 2, - max: 512, - }, - date: { - start: { - type: Date, - required: true, - }, - end: { - type: Date, - required: true, - }, - }, - url: { - type: String, - required: true, - min: 2, - max: 256, - }, - order: Number, - price: { - startingFrom: Number, - }, - }, - ], + events: { + type: [EventSchema], + default: [], + }, }, { timestamps: true } ); diff --git a/models/Profile/Event.js b/models/Profile/Event.js new file mode 100644 index 00000000000..e9558ae1edb --- /dev/null +++ b/models/Profile/Event.js @@ -0,0 +1,47 @@ +import mongoose, { Schema } from "mongoose"; + +const EventSchema = new Schema({ + isVirtual: Boolean, + name: { + type: String, + required: true, + min: 2, + max: 256, + }, + description: { + type: String, + required: true, + min: 2, + max: 512, + }, + date: { + type: new Schema({ + start: { + type: Date, + }, + end: { + type: Date, + }, + }), + required: true, + }, + url: { + type: String, + required: true, + min: 2, + max: 256, + }, + order: Number, + price: { + startingFrom: Number, + }, +}); + +EventSchema.pre("save", () => { + throw new Error("This is a nested document, no need to save it directly"); +}); + +module.exports = { + EventSchema, + Event: mongoose.models.Event || mongoose.model("Event", EventSchema), +}; diff --git a/models/Profile/Milestone.js b/models/Profile/Milestone.js new file mode 100644 index 00000000000..00cd8ec91ac --- /dev/null +++ b/models/Profile/Milestone.js @@ -0,0 +1,44 @@ +import mongoose, { Schema } from "mongoose"; + +const MilestoneSchema = new Schema({ + url: { + type: String, + required: false, + min: 2, + max: 256, + }, + date: { + type: Date, + required: true, + }, + isGoal: Boolean, + title: { + type: String, + required: true, + min: 2, + max: 256, + }, + icon: { + type: String, + required: true, + min: 2, + max: 32, + }, + description: { + type: String, + required: true, + min: 2, + max: 512, + }, + order: Number, +}); + +MilestoneSchema.pre("save", () => { + throw new Error("This is a nested document, no need to save it directly"); +}); + +module.exports = { + MilestoneSchema, + Milestone: + mongoose.models.Milestone || mongoose.model("Milestone", MilestoneSchema), +}; diff --git a/pages/account/manage/event/[[...data]].js b/pages/account/manage/event/[[...data]].js index 97730f44404..45d6fe7afcd 100644 --- a/pages/account/manage/event/[[...data]].js +++ b/pages/account/manage/event/[[...data]].js @@ -42,11 +42,18 @@ export async function getServerSideProps(context) { } export default function ManageEvent({ BASE_URL, event }) { - const [showNotification, setShowNotification] = useState(false); - const [isVirtual, setIsVirtual] = useState(event.isVirtual); - const [name, setName] = useState(event.name); - const [description, setDescription] = useState(event.description); - const [url, setUrl] = useState(event.url); + const [showNotification, setShowNotification] = useState({ + show: false, + type: "", + message: "", + additionalMessage: "", + }); + const [isVirtual, setIsVirtual] = useState(event.isVirtual || true); + const [name, setName] = useState(event.name || "Official name of the Event"); + const [description, setDescription] = useState( + event.description || "Description of the event from their website" + ); + const [url, setUrl] = useState(event.url || ""); const [startDate, setStartDate] = useState(event.date?.start); const [endDate, setEndDate] = useState(event.date?.end); const [price, setPrice] = useState(event.price); @@ -75,8 +82,25 @@ export default function ManageEvent({ BASE_URL, event }) { }, body: JSON.stringify(putEvent), }); - await res.json(); - setShowNotification(true); + const update = await res.json(); + + if (update.message) { + return setShowNotification({ + show: true, + type: "error", + message: "Event add/update failed", + additionalMessage: `Please check the fields: ${Object.keys( + update.message + ).join(", ")}`, + }); + } + + return setShowNotification({ + show: true, + type: "success", + message: "Event added/updated", + additionalMessage: "Your event has been added/updated successfully", + }); }; return ( @@ -90,11 +114,13 @@ export default function ManageEvent({ BASE_URL, event }) { setShowNotification(false)} - message="Event saved" - additionalMessage="Your Event information has been saved successfully." + show={showNotification.show} + type={showNotification.type} + onClose={() => + setShowNotification({ ...showNotification, show: false }) + } + message={showNotification.message} + additionalMessage={showNotification.additionalMessage} />
@@ -117,6 +143,9 @@ export default function ManageEvent({ BASE_URL, event }) { label="Event Name" onChange={(e) => setName(e.target.value)} value={name} + required + minLength="2" + maxLength="256" />

For example: EddieCon v0.1 @@ -132,18 +161,24 @@ export default function ManageEvent({ BASE_URL, event }) {

setUrl(e.target.value)} value={url} + required + minLength="2" + maxLength="256" />
setStartDate(e.target.value)} value={startDate} + required />

For example: 2022-12-09T16:00:00.000+00:00 @@ -151,13 +186,15 @@ export default function ManageEvent({ BASE_URL, event }) {

setEndDate(e.target.value)} value={endDate} + required />

- For example: 2022-12-10T16:00:00.000+00:00 + For example: DD / MM / YYYY

diff --git a/pages/account/manage/link/[[...data]].js b/pages/account/manage/link/[[...data]].js index 7aecf4ab44b..5853f772dd1 100644 --- a/pages/account/manage/link/[[...data]].js +++ b/pages/account/manage/link/[[...data]].js @@ -43,7 +43,12 @@ export async function getServerSideProps(context) { } export default function ManageLink({ BASE_URL, username, link }) { - const [showNotification, setShowNotification] = useState(false); + const [showNotification, setShowNotification] = useState({ + show: false, + type: "", + message: "", + additionalMessage: "", + }); const [edit, setEdit] = useState(link._id ? true : false); const [group, setGroup] = useState(link.group); const [name, setName] = useState(link.name); @@ -64,8 +69,25 @@ export default function ManageLink({ BASE_URL, username, link }) { body: JSON.stringify({ group, name, url, icon, isEnabled, isPinned }), } ); - await res.json(); - setShowNotification(true); + const update = await res.json(); + + if (update.message) { + return setShowNotification({ + show: true, + type: "error", + message: "Link add/update failed", + additionalMessage: `Please check the fields: ${Object.keys( + update.message + ).join(", ")}`, + }); + } + + return setShowNotification({ + show: true, + type: "success", + message: "Link added/updated", + additionalMessage: "Your Link has been added/updated successfully", + }); }; return ( @@ -79,11 +101,13 @@ export default function ManageLink({ BASE_URL, username, link }) { setShowNotification(false)} - message="Link saved" - additionalMessage="Your link information has been saved successfully." + show={showNotification.show} + type={showNotification.type} + onClose={() => + setShowNotification({ ...showNotification, show: false }) + } + message={showNotification.message} + additionalMessage={showNotification.additionalMessage} />
@@ -110,6 +134,8 @@ export default function ManageLink({ BASE_URL, username, link }) { label="Group" onChange={(e) => setGroup(e.target.value)} value={group} + minLength="2" + maxLength="64" />

You can{" "} @@ -124,12 +150,16 @@ export default function ManageLink({ BASE_URL, username, link }) {

setUrl(e.target.value)} value={url} disabled={edit} readOnly={edit} + required + minLength="2" + maxLength="256" />

For example: https://twitter.com/eddiejaoude @@ -141,6 +171,9 @@ export default function ManageLink({ BASE_URL, username, link }) { label="Display Name" onChange={(e) => setName(e.target.value)} value={name} + required + minLength="2" + maxLength="32" />

For example: Follow me on Twitter @@ -152,6 +185,9 @@ export default function ManageLink({ BASE_URL, username, link }) { label="Icon" onChange={(e) => setIcon(e.target.value)} value={icon} + required + minLength="2" + maxLength="32" />

Search for available{" "} diff --git a/pages/account/manage/milestone/[[...data]].js b/pages/account/manage/milestone/[[...data]].js index 80c60750bcc..82a4051a7dc 100644 --- a/pages/account/manage/milestone/[[...data]].js +++ b/pages/account/manage/milestone/[[...data]].js @@ -43,11 +43,20 @@ export async function getServerSideProps(context) { } export default function ManageMilestone({ BASE_URL, milestone }) { - const [showNotification, setShowNotification] = useState(false); - const [title, setTitle] = useState(milestone.title); - const [description, setDescription] = useState(milestone.description); - const [url, setUrl] = useState(milestone.url); - const [icon, setIcon] = useState(milestone.icon); + const [showNotification, setShowNotification] = useState({ + show: false, + type: "", + message: "", + additionalMessage: "", + }); + const [title, setTitle] = useState( + milestone.title || "Title of your Milestone" + ); + const [description, setDescription] = useState( + milestone.description || "Description of your Milestone" + ); + const [url, setUrl] = useState(milestone.url || ""); + const [icon, setIcon] = useState(milestone.icon || "FaGithub"); const [date, setDate] = useState(milestone.date); const [isGoal, setIsGoal] = useState(milestone.isGoal); @@ -75,8 +84,25 @@ export default function ManageMilestone({ BASE_URL, milestone }) { }, body: JSON.stringify(putMilestone), }); - await res.json(); - setShowNotification(true); + const update = await res.json(); + + if (update.message) { + return setShowNotification({ + show: true, + type: "error", + message: "Milestone update failed", + additionalMessage: `Please check the fields: ${Object.keys( + update.message + ).join(", ")}`, + }); + } + + return setShowNotification({ + show: true, + type: "success", + message: "Milestone added/updated", + additionalMessage: "Your milestone has been added/updated successfully", + }); }; return ( @@ -90,11 +116,13 @@ export default function ManageMilestone({ BASE_URL, milestone }) { setShowNotification(false)} - message="Milestone saved" - additionalMessage="Your milestone information has been saved successfully." + show={showNotification.show} + type={showNotification.type} + onClose={() => + setShowNotification({ ...showNotification, show: false }) + } + message={showNotification.message} + additionalMessage={showNotification.additionalMessage} />

@@ -117,6 +145,9 @@ export default function ManageMilestone({ BASE_URL, milestone }) { label="Milestone Title" onChange={(e) => setTitle(e.target.value)} value={title} + required + minLength="2" + maxLength="256" />

For example: GitHub Star @@ -128,6 +159,9 @@ export default function ManageMilestone({ BASE_URL, milestone }) { label="Description" onChange={(e) => setDescription(e.target.value)} value={description} + required + minLength="2" + maxLength="512" />

Describe this Milestone @@ -135,10 +169,13 @@ export default function ManageMilestone({ BASE_URL, milestone }) {

setUrl(e.target.value)} value={url} + minLength="2" + maxLength="256" />

Link to more information (optional) @@ -146,13 +183,15 @@ export default function ManageMilestone({ BASE_URL, milestone }) {

setDate(e.target.value)} value={date} + required />

- For example: May 2010 + For example: DD / MM / YYYY

@@ -161,6 +200,9 @@ export default function ManageMilestone({ BASE_URL, milestone }) { label="Icon" onChange={(e) => setIcon(e.target.value)} value={icon} + required + minLength="2" + maxLength="32" />

Search for available{" "} diff --git a/pages/account/manage/profile.js b/pages/account/manage/profile.js index e3e4f774411..02b87f5854b 100644 --- a/pages/account/manage/profile.js +++ b/pages/account/manage/profile.js @@ -63,11 +63,11 @@ export default function Profile({ BASE_URL, profile, fileExists }) { additionalMessage: "", }); const [layout, setLayout] = useState(profile.layout || "classic"); - const [name, setName] = useState(profile.name); + const [name, setName] = useState(profile.name || "Your name"); const [bio, setBio] = useState( profile.bio || "Have a look at my links below..." ); - const [tags, setTags] = useState(profile.tags || []); + const [tags, setTags] = useState(profile.tags || ["EddieHub"]); const layouts = ["classic", "inline"]; const handleSubmit = async (e) => { diff --git a/pages/api/account/manage/event/[[...data]].js b/pages/api/account/manage/event/[[...data]].js index fa78056dc64..7069f1dc37d 100644 --- a/pages/api/account/manage/event/[[...data]].js +++ b/pages/api/account/manage/event/[[...data]].js @@ -5,6 +5,7 @@ import { ObjectId } from "bson"; import connectMongo from "@config/mongo"; import logger from "@config/logger"; import Profile from "@models/Profile"; +import { Event } from "@models/Profile/Event"; export default async function handler(req, res) { const session = await getServerSession(req, res, authOptions); @@ -70,11 +71,19 @@ export async function getEventApi(username, id) { return JSON.parse(JSON.stringify(getEvent[0])); } -export async function updateEventApi(username, id, event) { +export async function updateEventApi(username, id, updateEvent) { await connectMongo(); const log = logger.child({ username }); let getEvent = {}; + + try { + await Event.validate(updateEvent, ["name", "description", "url", "date"]); + } catch (e) { + log.error(e, `validation failed to update event for username: ${username}`); + return { error: e.errors }; + } + try { getEvent = await Profile.findOneAndUpdate( { @@ -84,7 +93,7 @@ export async function updateEventApi(username, id, event) { { $set: { source: "database", - "events.$": event, + "events.$": updateEvent, }, }, { upsert: true } @@ -96,17 +105,26 @@ export async function updateEventApi(username, id, event) { return JSON.parse(JSON.stringify(getEvent)); } -export async function addEventApi(username, event) { +export async function addEventApi(username, addEvent) { await connectMongo(); const log = logger.child({ username }); + let getEvent = {}; + + try { + await Event.validate(addEvent, ["name", "description", "url", "date"]); + } catch (e) { + log.error(e, `validation failed to add event for username: ${username}`); + return { error: e.errors }; + } + try { getEvent = await Profile.findOneAndUpdate( { username, }, { - $push: { events: event }, + $push: { events: addEvent }, }, { upsert: true } ); diff --git a/pages/api/account/manage/link/[[...data]].js b/pages/api/account/manage/link/[[...data]].js index e20f343fd07..aec19677e0a 100644 --- a/pages/api/account/manage/link/[[...data]].js +++ b/pages/api/account/manage/link/[[...data]].js @@ -54,6 +54,17 @@ export async function updateLinkApi(username, url, data) { const log = logger.child({ username }); let getLink = {}; + + try { + await Link.validate(data, ["group", "name", "icon", "url"]); + } catch (e) { + log.error( + e, + `validation failed to add/update link for username: ${username}` + ); + return { error: e.errors }; + } + try { getLink = await Link.findOneAndUpdate( { diff --git a/pages/api/account/manage/milestone/[[...data]].js b/pages/api/account/manage/milestone/[[...data]].js index 578888acbd6..d5d32a3239f 100644 --- a/pages/api/account/manage/milestone/[[...data]].js +++ b/pages/api/account/manage/milestone/[[...data]].js @@ -5,6 +5,7 @@ import { ObjectId } from "bson"; import connectMongo from "@config/mongo"; import logger from "@config/logger"; import Profile from "@models/Profile"; +import { Milestone } from "@models/Profile/Milestone"; export default async function handler(req, res) { const session = await getServerSession(req, res, authOptions); @@ -26,9 +27,9 @@ export default async function handler(req, res) { } if (req.method === "PUT") { if (data?.length && data[0]) { - milestone = await updateMilstoneApi(username, data[0], req.body); + milestone = await updateMilestoneApi(username, data[0], req.body); } else { - milestone = await addMilstoneApi(username, req.body); + milestone = await addMilestoneApi(username, req.body); } } @@ -70,11 +71,28 @@ export async function getMilestoneApi(username, id) { return JSON.parse(JSON.stringify(getMilestone[0])); } -export async function updateMilstoneApi(username, id, milestone) { +export async function updateMilestoneApi(username, id, updateMilestone) { await connectMongo(); const log = logger.child({ username }); let getMilestone = {}; + + try { + await Milestone.validate(updateMilestone, [ + "url", + "date", + "title", + "icon", + "description", + ]); + } catch (e) { + log.error( + e, + `validation failed to update milestone for username: ${username}` + ); + return { error: e.errors }; + } + try { getMilestone = await Profile.findOneAndUpdate( { @@ -84,10 +102,10 @@ export async function updateMilstoneApi(username, id, milestone) { { $set: { source: "database", - "milestones.$": milestone, + "milestones.$": updateMilestone, }, }, - { upsert: true } + { upsert: true, new: true } ); } catch (e) { log.error(e, `failed to update milestone for username: ${username}`); @@ -96,22 +114,39 @@ export async function updateMilstoneApi(username, id, milestone) { return JSON.parse(JSON.stringify(getMilestone)); } -export async function addMilstoneApi(username, milestone) { +export async function addMilestoneApi(username, addMilestone) { await connectMongo(); const log = logger.child({ username }); let getMilestone = {}; + + try { + await Milestone.validate(addMilestone, [ + "url", + "date", + "title", + "icon", + "description", + ]); + } catch (e) { + log.error( + e, + `validation failed to add milestone for username: ${username}` + ); + return { error: e.errors }; + } + try { getMilestone = await Profile.findOneAndUpdate( { username, }, { - $push: { milestones: milestone }, + $push: { milestones: addMilestone }, }, - { upsert: true } + { upsert: true, new: true } ); } catch (e) { - log.error(e, `failed to update milestone for username: ${username}`); + log.error(e, `failed to add milestone for username: ${username}`); } return JSON.parse(JSON.stringify(getMilestone));