Skip to content

Commit

Permalink
Delete page + open graph metadata + header tweaked + static folder re…
Browse files Browse the repository at this point in the history
…move for the public folder
  • Loading branch information
borisghidaglia committed Aug 15, 2024
1 parent e98c683 commit 1f6d2c0
Show file tree
Hide file tree
Showing 26 changed files with 1,540 additions and 1,190 deletions.
3 changes: 3 additions & 0 deletions app/@header/default.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Header from "./page";

export default Header;
51 changes: 32 additions & 19 deletions app/@header/page.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,52 @@
"use client";

import Image from "next/image";
import Link from "next/link";
import { usePathname, useSearchParams } from "next/navigation";
import { Suspense } from "react";

import { getCities } from "@/app/_db/City";
import { City } from "@/app/_db/schema";
import { ExternalLink } from "@/components/ui/ExternalLink";

import logo from "@/static/logo.svg";
import logo from "@/public/logo.svg";

export default function SuspensedHeader() {
return (
<Suspense>
<Header />
</Suspense>
);
}

export default async function Header({
searchParams,
}: {
searchParams?: {
city?: string;
};
}) {
const cities: City[] = await getCities();
const selectedCity = searchParams?.city
? cities.find((city) => city.id == searchParams.city)
: undefined;
function Header() {
const searchParams = useSearchParams();
const city = searchParams.get("city");
const pathname = usePathname();

return (
<header>
<div className="flex items-center justify-between">
<div className="flex items-baseline justify-between">
<h1 className="flex items-baseline gap-2 text-3xl font-bold">
<Link href="/" className="contents">
<Image src={logo} alt="meet.hn logo" width="40" height="40" />
meet.hn
</Link>
</h1>
<ul className="flex gap-2">
{selectedCity ? (
<li>
<Link href="/">Register</Link>
</li>
{city || pathname !== "/" ? (
<>
<li>
<Link href="/" className="hover:underline">
Register
</Link>
</li>
<li>·</li>
</>
) : null}
<li>
<Link href="/delete" className="hover:underline">
Delete
</Link>
</li>
</ul>
</div>
<p className="text-sm text-gray-400">
Expand Down
3 changes: 3 additions & 0 deletions app/@map/default.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import MapPage from "./page";

export default MapPage;
17 changes: 12 additions & 5 deletions app/@map/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import dynamic from "next/dynamic";

import { getCities } from "@/app/_db/City";
import { City } from "@/app/_db/schema";
import { Suspense } from "react";

const MapContainer = dynamic(() => import("@/components/MapContainer"), {
ssr: false,
Expand All @@ -20,10 +21,16 @@ export default async function MapPage({
: undefined;

return (
<MapContainer
cities={cities}
selectedCity={selectedCity}
className="h-[400px] w-full md:h-full"
/>
<Suspense
fallback={
<MapContainer cities={[]} className="h-[400px] w-full md:h-full" />
}
>
<MapContainer
cities={cities}
selectedCity={selectedCity}
className="h-[400px] w-full md:h-full"
/>
</Suspense>
);
}
3 changes: 3 additions & 0 deletions app/@sidebar/default.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Home from "./page";

export default Home;
79 changes: 79 additions & 0 deletions app/@sidebar/delete/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"use client";

import { useEffect, useState } from "react";
import { useFormState } from "react-dom";

import { deleteUser } from "@/app/_actions/deleteUser";
import { SubmitButton } from "@/components/SubmitButton";
import { ExternalLink } from "@/components/ui/ExternalLink";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";

export default function DeletePage() {
const [username, setUsername] = useState<string>("");
const [isFormDisabled, setIsFormDisabled] = useState<boolean>();
const [formState, formAction] = useFormState(deleteUser, undefined);

useEffect(() => {
if (!formState?.wait) return;

setIsFormDisabled(true);
const timer = setTimeout(() => setIsFormDisabled(false), 1000 * 60);

return () => clearTimeout(timer);
}, [formState]);

return (
<div className="flex max-w-xl flex-col gap-2">
<p>
To delete your account, remove all the meet.hn informations from your HN
description and click on the button below.
</p>
<form action={formAction} className="flex flex-col gap-2">
<Input
name="username"
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="HN username"
className="border-[#99999a]"
/>
<div className="flex items-center justify-between gap-3">
{formState && Object.keys(formState).length > 0 && (
<div
className={cn(
"text-sm",
formState.success === true ? "text-green-800" : "text-red-800",
)}
>
{formState.message}
</div>
)}
<SubmitButton
disabled={isFormDisabled || !username}
className="ml-auto self-start bg-[#ff6602] hover:bg-[#e15b02]"
>
Remove me from the map
</SubmitButton>
</div>
<p>
Open your HN account here:
<br />
<ExternalLink
href={`https://news.ycombinator.com/user?id=${username.length === 0 ? "dang" : username}`}
className="font-medium"
>
{username.length === 0 ? "dang" : username}
</ExternalLink>
{username.length === 0 ? (
<span className="inline italic">
{" "}
⬅️ Placeholder username. Yours will be set here as soon as you
fill it above.
</span>
) : null}
</p>
</form>
</div>
);
}
6 changes: 3 additions & 3 deletions app/@sidebar/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ export default async function Home({
account and I&apos;ll retweet you.
</p>
<p>
To give some feedback or remove your profile from meet.hn, drop me a DM
on Twitter{" "}
To give some feedback, drop me a DM on Twitter{" "}
<ExternalLink
href="https://x.com/borisfyi"
target="_blank"
className="font-medium"
>
@borisfyi
</ExternalLink>
</ExternalLink>{" "}
or send a line to <b className="font-medium">[email protected]</b>
</p>
</>
);
Expand Down
13 changes: 3 additions & 10 deletions app/_actions/addUser.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
"use server";

import { decode } from "he";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";

import { getHnUserAboutSection } from "@/app/_actions/common/hn";
import {
decrementCityHackerCount,
fetchCity,
Expand All @@ -11,8 +14,6 @@ import {
} from "@/app/_db/City";
import { CityWithoutMetadata, UserWithoutMetadata } from "@/app/_db/schema";
import { getUser, saveUser } from "@/app/_db/User";
import { decode } from "he";
import { redirect } from "next/navigation";

export const addUser = async (prevState: unknown, formData: FormData) => {
const { username, location } = Object.fromEntries(formData);
Expand Down Expand Up @@ -116,11 +117,3 @@ async function saveUserAndCity(
incrementCityHackerCount(city.id),
]);
}

async function getHnUserAboutSection(username: string) {
const hnUser: null | { about: string } = await fetch(
`https://hacker-news.firebaseio.com/v0/user/${username}.json`,
).then((res) => res.json());

return hnUser?.about;
}
7 changes: 7 additions & 0 deletions app/_actions/common/hn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export async function getHnUserAboutSection(username: string) {
const hnUser: null | { about: string } = await fetch(
`https://hacker-news.firebaseio.com/v0/user/${username}.json`,
).then((res) => res.json());

return hnUser?.about;
}
82 changes: 82 additions & 0 deletions app/_actions/deleteUser.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"use server";

import { decode } from "he";
import { revalidatePath } from "next/cache";

import { getHnUserAboutSection } from "@/app/_actions/common/hn";
import { decrementCityHackerCount } from "@/app/_db/City";
import { deleteUser as deleteUserFromDb, getUser } from "@/app/_db/User";

export const deleteUser = async (
prevState: unknown,
formData: FormData,
): Promise<{
success: boolean;
message: JSX.Element | string;
wait?: boolean;
}> => {
const { username } = Object.fromEntries(formData);

if (typeof username !== "string" || username === "")
return {
success: false,
message:
"Can't remove your account if you don't give me your username 🤷",
};

const about = await getHnUserAboutSection(username);

if (!about) return await deleteUserAndUpdateCity(username);

const decodedAbout = decode(about);

const fullMeetHnData = decodedAbout.match(
/### meet\.hn\/\?city=(\S+)<p>([\s\S]*?)---/,
);
const cityOnlyMeetHnData = decodedAbout.match(
/meet\.hn\/\?city=([^\n]+)\s*\n?/,
);

if (fullMeetHnData && cityOnlyMeetHnData)
return {
success: false,
message: (
<p>
Some meet.hn data still exists in the user HN description.{" "}
<b>Waiting a minute to let HN API update.</b>
</p>
),
wait: true,
};

return await deleteUserAndUpdateCity(username);
};

const deleteUserAndUpdateCity = async (username: string) => {
const user = await getUser(username);
if (!user)
return {
success: false,
message: (
<p>
<b>{username}</b> isn&apos;t registered on meet.hn
</p>
),
};

// Delete user
await deleteUserFromDb(username);

// Update its city count
decrementCityHackerCount(user.cityId);
revalidatePath("/");

return {
success: true,
message: (
<p>
<b>{username}</b> has been deleted successfully ✅
</p>
),
};
};
24 changes: 21 additions & 3 deletions app/_db/User.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { GetCommand, PutCommand, QueryCommand } from "@aws-sdk/lib-dynamodb";
import {
DeleteCommand,
GetCommand,
PutCommand,
QueryCommand,
} from "@aws-sdk/lib-dynamodb";
import { decode } from "he";
import { cache } from "react";

import { docClient } from "@/app/_db/Client";
import { City, ClientUser, DbUser } from "@/app/_db/schema";
import { parseAtHnUrl, parseSocials } from "@/components/Socials";
import { parseTags } from "@/components/Tags";
import { docClient } from "./Client";
import { City, ClientUser, DbUser } from "./schema";

export const getUser = cache(async (username: string) => {
const getCommand = new GetCommand({
Expand Down Expand Up @@ -37,6 +42,19 @@ export const saveUser = async (
return response;
};

export const deleteUser = async (username: string) => {
const command = new DeleteCommand({
TableName: process.env.DYNAMODB_TABLE!,
Key: {
entityType: "USER",
entityId: username,
},
});

const response = await docClient.send(command);
return response;
};

export const getUsers = cache(async (city?: City) => {
const command = city
? new QueryCommand({
Expand Down
Loading

0 comments on commit 1f6d2c0

Please sign in to comment.