From d04b19c21e73703dd6cd3a66c597209a57d43847 Mon Sep 17 00:00:00 2001 From: Saahil Date: Sun, 1 Dec 2024 19:42:44 -0500 Subject: [PATCH 1/4] fix: no more dataurls --- src/app/harbor/shipyard/edit-ship-form.tsx | 130 ++++++------ src/app/harbor/shipyard/new-ship-form.tsx | 232 +++++++++++---------- src/middleware.ts | 5 +- 3 files changed, 190 insertions(+), 177 deletions(-) diff --git a/src/app/harbor/shipyard/edit-ship-form.tsx b/src/app/harbor/shipyard/edit-ship-form.tsx index 2c2f11a2..c05ee5b8 100644 --- a/src/app/harbor/shipyard/edit-ship-form.tsx +++ b/src/app/harbor/shipyard/edit-ship-form.tsx @@ -1,44 +1,44 @@ -import { Button, buttonVariants } from '@/components/ui/button' -import { deleteShip, updateShip } from './ship-utils' -import type { Ship } from '@/app/utils/data' -import { useToast } from '@/hooks/use-toast' -import Icon from '@hackclub/icons' -import React, { useState } from 'react' -import Modal from '../../../components/ui/modal' +import { Button, buttonVariants } from "@/components/ui/button"; +import { deleteShip, updateShip } from "./ship-utils"; +import type { Ship } from "@/app/utils/data"; +import { useToast } from "@/hooks/use-toast"; +import Icon from "@hackclub/icons"; +import React, { useState } from "react"; +import Modal from "../../../components/ui/modal"; const editMessages = [ - 'Orpheus hopes you know that she put a lot of effort into recording your changes~', - 'Heidi scribbles down your changes hastily...', + "Orpheus hopes you know that she put a lot of effort into recording your changes~", + "Heidi scribbles down your changes hastily...", "Orpheus put your Ship changes in the logbook. They're going nowhere, rest assured.", -] +]; const deleteMessages = [ - 'is no more!', - 'has been struck from the logbook', - 'has been lost to time...', -] + "is no more!", + "has been struck from the logbook", + "has been lost to time...", +]; export default function EditShipForm({ ship, closeForm, setShips, }: { - ship: Ship - closeForm: () => void - setShips: any + ship: Ship; + closeForm: () => void; + setShips: any; }) { - const [deleting, setDeleting] = useState(false) - const [openDeleteModal, setOpenDeleteModal] = useState(false) - const [saving, setSaving] = useState(false) - const allowDeletion = ship.shipStatus === 'staged' + const [deleting, setDeleting] = useState(false); + const [openDeleteModal, setOpenDeleteModal] = useState(false); + const [saving, setSaving] = useState(false); + const allowDeletion = ship.shipStatus === "staged"; - const { toast } = useToast() + const { toast } = useToast(); const handleSubmit = async (e) => { - setSaving(true) - e.preventDefault() - const formData = new FormData(e.target) - const formValues = Object.fromEntries(formData.entries()) + setSaving(true); + e.preventDefault(); + const formData = new FormData(e.target); + const formValues = Object.fromEntries(formData.entries()); const newShip: Ship = { ...ship, @@ -50,63 +50,63 @@ export default function EditShipForm({ deploymentUrl: formValues.deploymentUrl as string, readmeUrl: formValues.readmeUrl as string, screenshotUrl: formValues.screenshotUrl as string, - } - console.log('updating...', formValues, ship, newShip) - await updateShip(newShip) + }; + console.log("updating...", formValues, ship, newShip); + await updateShip(newShip); if (setShips) { - console.log('Set ships is passed! Updating ship with ID', newShip.id) + console.log("Set ships is passed! Updating ship with ID", newShip.id); setShips((previousShips: Ship[]) => { - console.log('the previous ships were', previousShips) + console.log("the previous ships were", previousShips); const newShips = previousShips.map((s: Ship) => - s.id === newShip.id ? newShip : s, - ) + s.id === newShip.id ? newShip : s + ); - setSaving(false) - return newShips - }) + setSaving(false); + return newShips; + }); } else { - console.error("Updated a ship but can't setShips bc you didn't pass it.") + console.error("Updated a ship but can't setShips bc you didn't pass it."); } - closeForm() + closeForm(); toast({ - title: 'Ship updated!', + title: "Ship updated!", description: editMessages[Math.floor(Math.random() * editMessages.length)], - }) + }); - setSaving(false) - } + setSaving(false); + }; const handleDelete = async (e) => { - setDeleting(true) + setDeleting(true); - e.preventDefault() - console.log('trying to delete ', ship.id, ship.title) - await deleteShip(ship.id) + e.preventDefault(); + console.log("trying to delete ", ship.id, ship.title); + await deleteShip(ship.id); if (setShips) { - console.log(`Deleted ${ship.title} (${ship.id})`) + console.log(`Deleted ${ship.title} (${ship.id})`); setShips((previousShips: Ship[]) => - previousShips.filter((s: Ship) => s.id !== ship.id), - ) + previousShips.filter((s: Ship) => s.id !== ship.id) + ); } else { - console.error("Deleted a ship but can't setShips bc you didn't pass it.") + console.error("Deleted a ship but can't setShips bc you didn't pass it."); } - closeForm() + closeForm(); toast({ - title: 'Ship deleted!', - description: `${ship.shipType === 'update' ? 'Your update to ' : ''}${ + title: "Ship deleted!", + description: `${ship.shipType === "update" ? "Your update to " : ""}${ ship.title } ${deleteMessages[Math.floor(Math.random() * deleteMessages.length)]}`, - }) + }); - setDeleting(false) - } + setDeleting(false); + }; return (

- No dataurls please. If you need an image host, upload your image in{' '} + No dataurls please. If you need an image host, upload your image in{" "} #cdn

- ) + ); } diff --git a/src/app/harbor/shipyard/new-ship-form.tsx b/src/app/harbor/shipyard/new-ship-form.tsx index 191324ab..3d187e9f 100644 --- a/src/app/harbor/shipyard/new-ship-form.tsx +++ b/src/app/harbor/shipyard/new-ship-form.tsx @@ -1,40 +1,40 @@ // Import necessary modules and components -import React from 'react' -import Link from 'next/link' -import { createShip, Ship } from './ship-utils' -import { Button } from '@/components/ui/button' -import JSConfetti from 'js-confetti' -import { useEffect, useRef, useState } from 'react' -import { getWakaSessions } from '@/app/utils/waka' -import { AnimatePresence, motion } from 'framer-motion' -import { useToast } from '@/hooks/use-toast' -import Icon from '@hackclub/icons' -import { MultiSelect } from '../../../components/ui/multi-select' +import React from "react"; +import Link from "next/link"; +import { createShip, Ship } from "./ship-utils"; +import { Button } from "@/components/ui/button"; +import JSConfetti from "js-confetti"; +import { useEffect, useRef, useState } from "react"; +import { getWakaSessions } from "@/app/utils/waka"; +import { AnimatePresence, motion } from "framer-motion"; +import { useToast } from "@/hooks/use-toast"; +import Icon from "@hackclub/icons"; +import { MultiSelect } from "../../../components/ui/multi-select"; async function testReadmeLink(url: string) { - const response = await fetch(url) + const response = await fetch(url); if (response.status !== 200) { - return false + return false; } - const responseText = await response.text() - if (!responseText || responseText === '404: Not Found') { - return false + const responseText = await response.text(); + if (!responseText || responseText === "404: Not Found") { + return false; } - return true + return true; } async function getReadmeFromRepo(url: string) { - if (!url.includes('github.com')) { - return null + if (!url.includes("github.com")) { + return null; } // https://api.github.com/repos/OWNER/REPO/readme const readmeUrl = url.replace( /https:\/\/github.com\/(.*?)\/(.*?)\/?$/, - 'https://api.github.com/repos/$1/$2/readme', - ) - const readmeData = await fetch(readmeUrl).then((d) => d.json()) - const readmeURI = readmeData.download_url - return (await testReadmeLink(readmeURI)) ? readmeURI : null + "https://api.github.com/repos/$1/$2/readme" + ); + const readmeData = await fetch(readmeUrl).then((d) => d.json()); + const readmeURI = readmeData.download_url; + return (await testReadmeLink(readmeURI)) ? readmeURI : null; } export default function NewShipForm({ @@ -44,171 +44,181 @@ export default function NewShipForm({ session, ...props }: { - ships: Ship[] - canvasRef: any - closeForm: any - session: any + ships: Ship[]; + canvasRef: any; + closeForm: any; + session: any; }) { - const [staging, setStaging] = useState(false) - const confettiRef = useRef(null) - const [usedRepos, setUsedRepos] = useState([]) + const [staging, setStaging] = useState(false); + const confettiRef = useRef(null); + const [usedRepos, setUsedRepos] = useState([]); const [projects, setProjects] = useState< { key: string; total: number }[] | null - >(null) + >(null); const [selectedProjects, setSelectedProjects] = useState< | [ { - key: string - total: number - }, + key: string; + total: number; + } ] | null - >(null) - const [isShipUpdate, setIsShipUpdate] = useState(false) - const { toast } = useToast() + >(null); + const [isShipUpdate, setIsShipUpdate] = useState(false); + const { toast } = useToast(); // Initialize confetti on mount useEffect(() => { - confettiRef.current = new JSConfetti({ canvas: canvasRef.current }) - }, [canvasRef.current]) + confettiRef.current = new JSConfetti({ canvas: canvasRef.current }); + }, [canvasRef.current]); // Fetch projects from the API using the Slack ID useEffect(() => { async function fetchProjects() { try { - if (sessionStorage.getItem('tutorial') === 'true') { - setProjects([{ key: 'hack-club-site', total: 123 * 60 * 60 }]) + if (sessionStorage.getItem("tutorial") === "true") { + setProjects([{ key: "hack-club-site", total: 123 * 60 * 60 }]); } else { - const res = await getWakaSessions() + const res = await getWakaSessions(); const shippedShips = ships - .filter((s) => s.shipStatus !== 'deleted') - .flatMap((s) => s.wakatimeProjectNames) + .filter((s) => s.shipStatus !== "deleted") + .flatMap((s) => s.wakatimeProjectNames); setProjects( res.projects.filter( (p: { key: string; total: number }) => - p.key !== '<>' && !shippedShips.includes(p.key), - ), - ) + p.key !== "<>" && !shippedShips.includes(p.key) + ) + ); } } catch (error) { - console.error('Error fetching projects:', error) + console.error("Error fetching projects:", error); } } - fetchProjects() - }, [ships]) + fetchProjects(); + }, [ships]); const handleForm = async (formData: FormData) => { - setStaging(true) + setStaging(true); - const deploymentUrl = formData.get('deployment_url') as string + const deploymentUrl = formData.get("deployment_url") as string; if ( - ['github.com', 'gitlab.com', 'bitbucket.org'].some((domain) => - deploymentUrl.includes(domain), + ["github.com", "gitlab.com", "bitbucket.org"].some((domain) => + deploymentUrl.includes(domain) ) ) { toast({ title: "That's not a demo link!", description: - 'Submit a link to a deployed project or a video demo of what your project is instead!', - }) - setStaging(false) - return + "Submit a link to a deployed project or a video demo of what your project is instead!", + }); + setStaging(false); + return; } - const repoUrl = formData.get('repo_url') as string + const repoUrl = formData.get("repo_url") as string; if (usedRepos.includes(repoUrl)) { toast({ - title: 'You already submitted a project from this repo!', + title: "You already submitted a project from this repo!", description: "If you're shipping an update to a project, use the 'ship an update' button instead.", - }) + }); } - const screenshotUrl = formData.get('screenshot_url') as string - const readmeUrl = formData.get('readme_url') as string + const screenshotUrl = formData.get("screenshot_url") as string; + const readmeUrl = formData.get("readme_url") as string; const [screenshotRes, readmeRes] = await Promise.all([ fetch(screenshotUrl).catch((e) => console.error(e)), fetch(readmeUrl).catch((e) => console.error(e)), - ]) + ]); if (!screenshotRes) { toast({ title: "We couldn't load your screenshot link!", - description: 'Try #cdn instead!', - }) - setStaging(false) - return + description: "Try #cdn instead!", + }); + setStaging(false); + return; } - if (!screenshotRes?.headers?.get('content-type')?.startsWith('image')) { + if (!screenshotRes?.headers?.get("content-type")?.startsWith("image")) { toast({ title: "That's not an image!", - description: 'Submit a link to an image of your project instead!', - }) - setStaging(false) - return + description: "Submit a link to an image of your project instead!", + }); + setStaging(false); + return; } - if (screenshotUrl.includes('cdn.discordapp.com')) { + if (screenshotUrl.includes("cdn.discordapp.com")) { toast({ title: "That screenshot doesn't work!", description: - 'Discord links are temporary, please host your files in #cdn!', - }) - setStaging(false) - return + "Discord links are temporary, please host your files in #cdn!", + }); + setStaging(false); + return; } - if (readmeUrl.includes('github.com')) { + if (!screenshotUrl.startsWith("http")) { + toast({ + title: "That screenshot doesn't work!", + description: + "Please use http or https links (no data urls), please host your files in #cdn!", + }); + setStaging(false); + return; + } + + if (readmeUrl.includes("github.com")) { toast({ title: "This isn't a markdown link!", description: - 'Submit a link to the raw README file in your repo instead!', - }) - setStaging(false) - return + "Submit a link to the raw README file in your repo instead!", + }); + setStaging(false); + return; } if ( readmeRes.status !== 200 || - !['text/plain', 'text/markdown'].includes( - readmeRes?.headers?.get('content-type')?.split(';')[0] || '', + !["text/plain", "text/markdown"].includes( + readmeRes?.headers?.get("content-type")?.split(";")[0] || "" ) ) { toast({ title: "That's not a valid README link!", - description: 'Submit a link to a README file in your repo instead!', - }) - setStaging(false) - return + description: "Submit a link to a README file in your repo instead!", + }); + setStaging(false); + return; } - const isTutorial = sessionStorage?.getItem('tutorial') === 'true' - confettiRef.current?.addConfetti() - closeForm() + const isTutorial = sessionStorage?.getItem("tutorial") === "true"; + confettiRef.current?.addConfetti(); + closeForm(); if (isTutorial) { - window.location.reload() + window.location.reload(); } else { - const _newShip = await createShip(formData, false) - setStaging(false) + const _newShip = await createShip(formData, false); + setStaging(false); } // ideally we don't have to reload the page here. - window.location.reload() - } + window.location.reload(); + }; const projectDropdownList = projects?.map((p: any) => ({ label: `${p.key} (${(p.total / 60 / 60).toFixed(2)} hrs)`, value: p.key, icon: () => , - })) + })); return (

- {isShipUpdate ? 'Update a' : 'New'} Ship + {isShipUpdate ? "Update a" : "New"} Ship

@@ -227,7 +237,7 @@ export default function NewShipForm({
For example: maybe you already built a game, and you want to ship an amazing update to it! Click this box and describe the update. - If you {"don't"} understand this, please ask in{' '} + If you {"don't"} understand this, please ask in{" "}
@@ -312,12 +322,12 @@ export default function NewShipForm({ onChange={({ target }) => { getReadmeFromRepo(target.value).then((readme) => { if (readme && document) { - const readmeEl = document.getElementById('readme_url') + const readmeEl = document.getElementById("readme_url"); if (readmeEl) { - readmeEl.setAttribute('value', readme) + readmeEl.setAttribute("value", readme); } } - }) + }); }} />
@@ -351,7 +361,7 @@ export default function NewShipForm({ Screenshot URL
- No dataURLs please. You can upload to{' '} + No dataURLs please. You can upload to{" "} #cdn - {' '} + {" "} if you like! @@ -379,7 +389,7 @@ export default function NewShipForm({ Staging! ) : ( - 'Submit as a draft' + "Submit as a draft" )}

@@ -388,5 +398,5 @@ export default function NewShipForm({

- ) + ); } diff --git a/src/middleware.ts b/src/middleware.ts index 457c3912..ae9402b0 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -22,7 +22,10 @@ async function loadShipsCookie( const ships = await fetchShips(slackId, 3) response.cookies.set({ name: 'ships', - value: JSON.stringify(ships), + value: JSON.stringify(ships.map(s => { + if (s.screenshotUrl.startsWith('data:')) s.screenshotUrl = `replace_me_with_a_non_dataencoded_url_plz` + return s; + })), path: '/', sameSite: 'strict', expires: new Date(Date.now() + 5 * 60 * 1000), // In 5 mins From dbb1f38e7a5437db191a4710b4ce139acbb66325 Mon Sep 17 00:00:00 2001 From: Saahil Date: Sun, 1 Dec 2024 19:46:19 -0500 Subject: [PATCH 2/4] fix: lint --- src/app/harbor/shipyard/edit-ship-form.tsx | 130 ++++++------ src/app/harbor/shipyard/new-ship-form.tsx | 232 ++++++++++----------- src/middleware.ts | 11 +- 3 files changed, 188 insertions(+), 185 deletions(-) diff --git a/src/app/harbor/shipyard/edit-ship-form.tsx b/src/app/harbor/shipyard/edit-ship-form.tsx index c05ee5b8..2c2f11a2 100644 --- a/src/app/harbor/shipyard/edit-ship-form.tsx +++ b/src/app/harbor/shipyard/edit-ship-form.tsx @@ -1,44 +1,44 @@ -import { Button, buttonVariants } from "@/components/ui/button"; -import { deleteShip, updateShip } from "./ship-utils"; -import type { Ship } from "@/app/utils/data"; -import { useToast } from "@/hooks/use-toast"; -import Icon from "@hackclub/icons"; -import React, { useState } from "react"; -import Modal from "../../../components/ui/modal"; +import { Button, buttonVariants } from '@/components/ui/button' +import { deleteShip, updateShip } from './ship-utils' +import type { Ship } from '@/app/utils/data' +import { useToast } from '@/hooks/use-toast' +import Icon from '@hackclub/icons' +import React, { useState } from 'react' +import Modal from '../../../components/ui/modal' const editMessages = [ - "Orpheus hopes you know that she put a lot of effort into recording your changes~", - "Heidi scribbles down your changes hastily...", + 'Orpheus hopes you know that she put a lot of effort into recording your changes~', + 'Heidi scribbles down your changes hastily...', "Orpheus put your Ship changes in the logbook. They're going nowhere, rest assured.", -]; +] const deleteMessages = [ - "is no more!", - "has been struck from the logbook", - "has been lost to time...", -]; + 'is no more!', + 'has been struck from the logbook', + 'has been lost to time...', +] export default function EditShipForm({ ship, closeForm, setShips, }: { - ship: Ship; - closeForm: () => void; - setShips: any; + ship: Ship + closeForm: () => void + setShips: any }) { - const [deleting, setDeleting] = useState(false); - const [openDeleteModal, setOpenDeleteModal] = useState(false); - const [saving, setSaving] = useState(false); - const allowDeletion = ship.shipStatus === "staged"; + const [deleting, setDeleting] = useState(false) + const [openDeleteModal, setOpenDeleteModal] = useState(false) + const [saving, setSaving] = useState(false) + const allowDeletion = ship.shipStatus === 'staged' - const { toast } = useToast(); + const { toast } = useToast() const handleSubmit = async (e) => { - setSaving(true); - e.preventDefault(); - const formData = new FormData(e.target); - const formValues = Object.fromEntries(formData.entries()); + setSaving(true) + e.preventDefault() + const formData = new FormData(e.target) + const formValues = Object.fromEntries(formData.entries()) const newShip: Ship = { ...ship, @@ -50,63 +50,63 @@ export default function EditShipForm({ deploymentUrl: formValues.deploymentUrl as string, readmeUrl: formValues.readmeUrl as string, screenshotUrl: formValues.screenshotUrl as string, - }; - console.log("updating...", formValues, ship, newShip); - await updateShip(newShip); + } + console.log('updating...', formValues, ship, newShip) + await updateShip(newShip) if (setShips) { - console.log("Set ships is passed! Updating ship with ID", newShip.id); + console.log('Set ships is passed! Updating ship with ID', newShip.id) setShips((previousShips: Ship[]) => { - console.log("the previous ships were", previousShips); + console.log('the previous ships were', previousShips) const newShips = previousShips.map((s: Ship) => - s.id === newShip.id ? newShip : s - ); + s.id === newShip.id ? newShip : s, + ) - setSaving(false); - return newShips; - }); + setSaving(false) + return newShips + }) } else { - console.error("Updated a ship but can't setShips bc you didn't pass it."); + console.error("Updated a ship but can't setShips bc you didn't pass it.") } - closeForm(); + closeForm() toast({ - title: "Ship updated!", + title: 'Ship updated!', description: editMessages[Math.floor(Math.random() * editMessages.length)], - }); + }) - setSaving(false); - }; + setSaving(false) + } const handleDelete = async (e) => { - setDeleting(true); + setDeleting(true) - e.preventDefault(); - console.log("trying to delete ", ship.id, ship.title); - await deleteShip(ship.id); + e.preventDefault() + console.log('trying to delete ', ship.id, ship.title) + await deleteShip(ship.id) if (setShips) { - console.log(`Deleted ${ship.title} (${ship.id})`); + console.log(`Deleted ${ship.title} (${ship.id})`) setShips((previousShips: Ship[]) => - previousShips.filter((s: Ship) => s.id !== ship.id) - ); + previousShips.filter((s: Ship) => s.id !== ship.id), + ) } else { - console.error("Deleted a ship but can't setShips bc you didn't pass it."); + console.error("Deleted a ship but can't setShips bc you didn't pass it.") } - closeForm(); + closeForm() toast({ - title: "Ship deleted!", - description: `${ship.shipType === "update" ? "Your update to " : ""}${ + title: 'Ship deleted!', + description: `${ship.shipType === 'update' ? 'Your update to ' : ''}${ ship.title } ${deleteMessages[Math.floor(Math.random() * deleteMessages.length)]}`, - }); + }) - setDeleting(false); - }; + setDeleting(false) + } return (

- No dataurls please. If you need an image host, upload your image in{" "} + No dataurls please. If you need an image host, upload your image in{' '} #cdn

- ); + ) } diff --git a/src/app/harbor/shipyard/new-ship-form.tsx b/src/app/harbor/shipyard/new-ship-form.tsx index 3d187e9f..b374c4f4 100644 --- a/src/app/harbor/shipyard/new-ship-form.tsx +++ b/src/app/harbor/shipyard/new-ship-form.tsx @@ -1,40 +1,40 @@ // Import necessary modules and components -import React from "react"; -import Link from "next/link"; -import { createShip, Ship } from "./ship-utils"; -import { Button } from "@/components/ui/button"; -import JSConfetti from "js-confetti"; -import { useEffect, useRef, useState } from "react"; -import { getWakaSessions } from "@/app/utils/waka"; -import { AnimatePresence, motion } from "framer-motion"; -import { useToast } from "@/hooks/use-toast"; -import Icon from "@hackclub/icons"; -import { MultiSelect } from "../../../components/ui/multi-select"; +import React from 'react' +import Link from 'next/link' +import { createShip, Ship } from './ship-utils' +import { Button } from '@/components/ui/button' +import JSConfetti from 'js-confetti' +import { useEffect, useRef, useState } from 'react' +import { getWakaSessions } from '@/app/utils/waka' +import { AnimatePresence, motion } from 'framer-motion' +import { useToast } from '@/hooks/use-toast' +import Icon from '@hackclub/icons' +import { MultiSelect } from '../../../components/ui/multi-select' async function testReadmeLink(url: string) { - const response = await fetch(url); + const response = await fetch(url) if (response.status !== 200) { - return false; + return false } - const responseText = await response.text(); - if (!responseText || responseText === "404: Not Found") { - return false; + const responseText = await response.text() + if (!responseText || responseText === '404: Not Found') { + return false } - return true; + return true } async function getReadmeFromRepo(url: string) { - if (!url.includes("github.com")) { - return null; + if (!url.includes('github.com')) { + return null } // https://api.github.com/repos/OWNER/REPO/readme const readmeUrl = url.replace( /https:\/\/github.com\/(.*?)\/(.*?)\/?$/, - "https://api.github.com/repos/$1/$2/readme" - ); - const readmeData = await fetch(readmeUrl).then((d) => d.json()); - const readmeURI = readmeData.download_url; - return (await testReadmeLink(readmeURI)) ? readmeURI : null; + 'https://api.github.com/repos/$1/$2/readme', + ) + const readmeData = await fetch(readmeUrl).then((d) => d.json()) + const readmeURI = readmeData.download_url + return (await testReadmeLink(readmeURI)) ? readmeURI : null } export default function NewShipForm({ @@ -44,181 +44,181 @@ export default function NewShipForm({ session, ...props }: { - ships: Ship[]; - canvasRef: any; - closeForm: any; - session: any; + ships: Ship[] + canvasRef: any + closeForm: any + session: any }) { - const [staging, setStaging] = useState(false); - const confettiRef = useRef(null); - const [usedRepos, setUsedRepos] = useState([]); + const [staging, setStaging] = useState(false) + const confettiRef = useRef(null) + const [usedRepos, setUsedRepos] = useState([]) const [projects, setProjects] = useState< { key: string; total: number }[] | null - >(null); + >(null) const [selectedProjects, setSelectedProjects] = useState< | [ { - key: string; - total: number; - } + key: string + total: number + }, ] | null - >(null); - const [isShipUpdate, setIsShipUpdate] = useState(false); - const { toast } = useToast(); + >(null) + const [isShipUpdate, setIsShipUpdate] = useState(false) + const { toast } = useToast() // Initialize confetti on mount useEffect(() => { - confettiRef.current = new JSConfetti({ canvas: canvasRef.current }); - }, [canvasRef.current]); + confettiRef.current = new JSConfetti({ canvas: canvasRef.current }) + }, [canvasRef.current]) // Fetch projects from the API using the Slack ID useEffect(() => { async function fetchProjects() { try { - if (sessionStorage.getItem("tutorial") === "true") { - setProjects([{ key: "hack-club-site", total: 123 * 60 * 60 }]); + if (sessionStorage.getItem('tutorial') === 'true') { + setProjects([{ key: 'hack-club-site', total: 123 * 60 * 60 }]) } else { - const res = await getWakaSessions(); + const res = await getWakaSessions() const shippedShips = ships - .filter((s) => s.shipStatus !== "deleted") - .flatMap((s) => s.wakatimeProjectNames); + .filter((s) => s.shipStatus !== 'deleted') + .flatMap((s) => s.wakatimeProjectNames) setProjects( res.projects.filter( (p: { key: string; total: number }) => - p.key !== "<>" && !shippedShips.includes(p.key) - ) - ); + p.key !== '<>' && !shippedShips.includes(p.key), + ), + ) } } catch (error) { - console.error("Error fetching projects:", error); + console.error('Error fetching projects:', error) } } - fetchProjects(); - }, [ships]); + fetchProjects() + }, [ships]) const handleForm = async (formData: FormData) => { - setStaging(true); + setStaging(true) - const deploymentUrl = formData.get("deployment_url") as string; + const deploymentUrl = formData.get('deployment_url') as string if ( - ["github.com", "gitlab.com", "bitbucket.org"].some((domain) => - deploymentUrl.includes(domain) + ['github.com', 'gitlab.com', 'bitbucket.org'].some((domain) => + deploymentUrl.includes(domain), ) ) { toast({ title: "That's not a demo link!", description: - "Submit a link to a deployed project or a video demo of what your project is instead!", - }); - setStaging(false); - return; + 'Submit a link to a deployed project or a video demo of what your project is instead!', + }) + setStaging(false) + return } - const repoUrl = formData.get("repo_url") as string; + const repoUrl = formData.get('repo_url') as string if (usedRepos.includes(repoUrl)) { toast({ - title: "You already submitted a project from this repo!", + title: 'You already submitted a project from this repo!', description: "If you're shipping an update to a project, use the 'ship an update' button instead.", - }); + }) } - const screenshotUrl = formData.get("screenshot_url") as string; - const readmeUrl = formData.get("readme_url") as string; + const screenshotUrl = formData.get('screenshot_url') as string + const readmeUrl = formData.get('readme_url') as string const [screenshotRes, readmeRes] = await Promise.all([ fetch(screenshotUrl).catch((e) => console.error(e)), fetch(readmeUrl).catch((e) => console.error(e)), - ]); + ]) if (!screenshotRes) { toast({ title: "We couldn't load your screenshot link!", - description: "Try #cdn instead!", - }); - setStaging(false); - return; + description: 'Try #cdn instead!', + }) + setStaging(false) + return } - if (!screenshotRes?.headers?.get("content-type")?.startsWith("image")) { + if (!screenshotRes?.headers?.get('content-type')?.startsWith('image')) { toast({ title: "That's not an image!", - description: "Submit a link to an image of your project instead!", - }); - setStaging(false); - return; + description: 'Submit a link to an image of your project instead!', + }) + setStaging(false) + return } - if (screenshotUrl.includes("cdn.discordapp.com")) { + if (screenshotUrl.includes('cdn.discordapp.com')) { toast({ title: "That screenshot doesn't work!", description: - "Discord links are temporary, please host your files in #cdn!", - }); - setStaging(false); - return; + 'Discord links are temporary, please host your files in #cdn!', + }) + setStaging(false) + return } - if (!screenshotUrl.startsWith("http")) { + if (!screenshotUrl.startsWith('http')) { toast({ title: "That screenshot doesn't work!", description: - "Please use http or https links (no data urls), please host your files in #cdn!", - }); - setStaging(false); - return; + 'Please use http or https links (no data urls), please host your files in #cdn!', + }) + setStaging(false) + return } - if (readmeUrl.includes("github.com")) { + if (readmeUrl.includes('github.com')) { toast({ title: "This isn't a markdown link!", description: - "Submit a link to the raw README file in your repo instead!", - }); - setStaging(false); - return; + 'Submit a link to the raw README file in your repo instead!', + }) + setStaging(false) + return } if ( readmeRes.status !== 200 || - !["text/plain", "text/markdown"].includes( - readmeRes?.headers?.get("content-type")?.split(";")[0] || "" + !['text/plain', 'text/markdown'].includes( + readmeRes?.headers?.get('content-type')?.split(';')[0] || '', ) ) { toast({ title: "That's not a valid README link!", - description: "Submit a link to a README file in your repo instead!", - }); - setStaging(false); - return; + description: 'Submit a link to a README file in your repo instead!', + }) + setStaging(false) + return } - const isTutorial = sessionStorage?.getItem("tutorial") === "true"; - confettiRef.current?.addConfetti(); - closeForm(); + const isTutorial = sessionStorage?.getItem('tutorial') === 'true' + confettiRef.current?.addConfetti() + closeForm() if (isTutorial) { - window.location.reload(); + window.location.reload() } else { - const _newShip = await createShip(formData, false); - setStaging(false); + const _newShip = await createShip(formData, false) + setStaging(false) } // ideally we don't have to reload the page here. - window.location.reload(); - }; + window.location.reload() + } const projectDropdownList = projects?.map((p: any) => ({ label: `${p.key} (${(p.total / 60 / 60).toFixed(2)} hrs)`, value: p.key, icon: () => , - })); + })) return (

- {isShipUpdate ? "Update a" : "New"} Ship + {isShipUpdate ? 'Update a' : 'New'} Ship

@@ -237,7 +237,7 @@ export default function NewShipForm({
For example: maybe you already built a game, and you want to ship an amazing update to it! Click this box and describe the update. - If you {"don't"} understand this, please ask in{" "} + If you {"don't"} understand this, please ask in{' '}
@@ -322,12 +322,12 @@ export default function NewShipForm({ onChange={({ target }) => { getReadmeFromRepo(target.value).then((readme) => { if (readme && document) { - const readmeEl = document.getElementById("readme_url"); + const readmeEl = document.getElementById('readme_url') if (readmeEl) { - readmeEl.setAttribute("value", readme); + readmeEl.setAttribute('value', readme) } } - }); + }) }} />
@@ -361,7 +361,7 @@ export default function NewShipForm({ Screenshot URL
- No dataURLs please. You can upload to{" "} + No dataURLs please. You can upload to{' '} #cdn - {" "} + {' '} if you like! @@ -389,7 +389,7 @@ export default function NewShipForm({ Staging! ) : ( - "Submit as a draft" + 'Submit as a draft' )}

@@ -398,5 +398,5 @@ export default function NewShipForm({

- ); + ) } diff --git a/src/middleware.ts b/src/middleware.ts index ae9402b0..1705eba7 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -22,10 +22,13 @@ async function loadShipsCookie( const ships = await fetchShips(slackId, 3) response.cookies.set({ name: 'ships', - value: JSON.stringify(ships.map(s => { - if (s.screenshotUrl.startsWith('data:')) s.screenshotUrl = `replace_me_with_a_non_dataencoded_url_plz` - return s; - })), + value: JSON.stringify( + ships.map((s) => { + if (s.screenshotUrl.startsWith('data:')) + s.screenshotUrl = `replace_me_with_a_non_dataencoded_url_plz` + return s + }), + ), path: '/', sameSite: 'strict', expires: new Date(Date.now() + 5 * 60 * 1000), // In 5 mins From f565725bba40bf235a8add3b52db488327c4da46 Mon Sep 17 00:00:00 2001 From: Saahil Date: Sun, 1 Dec 2024 20:50:59 -0500 Subject: [PATCH 3/4] fix: no more localhost --- src/app/harbor/shipyard/new-ship-form.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/app/harbor/shipyard/new-ship-form.tsx b/src/app/harbor/shipyard/new-ship-form.tsx index b374c4f4..c01161d5 100644 --- a/src/app/harbor/shipyard/new-ship-form.tsx +++ b/src/app/harbor/shipyard/new-ship-form.tsx @@ -166,6 +166,18 @@ export default function NewShipForm({ setStaging(false) return } + if ( + deploymentUrl.includes('localhost') || + deploymentUrl.includes('127.0.0.1') + ) { + toast({ + title: "That's not a demo link!", + description: + 'Please make sure your link isnt a local link.. Please submit a deployed link instead!', + }) + setStaging(false) + return + } if (readmeUrl.includes('github.com')) { toast({ From dfb05b617e76535305073d230f0625c25884581d Mon Sep 17 00:00:00 2001 From: Saahil Date: Sun, 1 Dec 2024 22:30:40 -0500 Subject: [PATCH 4/4] fix: https:// only now --- src/app/harbor/shipyard/new-ship-form.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/harbor/shipyard/new-ship-form.tsx b/src/app/harbor/shipyard/new-ship-form.tsx index c01161d5..d503974b 100644 --- a/src/app/harbor/shipyard/new-ship-form.tsx +++ b/src/app/harbor/shipyard/new-ship-form.tsx @@ -157,7 +157,7 @@ export default function NewShipForm({ return } - if (!screenshotUrl.startsWith('http')) { + if (!screenshotUrl.startsWith('https://')) { toast({ title: "That screenshot doesn't work!", description: