diff --git a/apps/social/package.json b/apps/social/package.json index 634e8263..b1f0aae2 100644 --- a/apps/social/package.json +++ b/apps/social/package.json @@ -27,6 +27,7 @@ "@urql/next": "^1.1.1", "@whatwg-node/server": "^0.9.46", "arctic": "^1.8.1", + "date-fns": "^3.6.0", "geist": "^1.3.1", "gql.tada": "^1.8.5", "graphql": "^16.9.0", diff --git a/apps/social/src/app/(authentication)/login/page.tsx b/apps/social/src/app/(authentication)/login/page.tsx index 36540fe9..4b9e9cd3 100644 --- a/apps/social/src/app/(authentication)/login/page.tsx +++ b/apps/social/src/app/(authentication)/login/page.tsx @@ -45,7 +45,7 @@ export default async function Login() { } return ( -
+
diff --git a/apps/social/src/app/(authentication)/register/page.tsx b/apps/social/src/app/(authentication)/register/page.tsx index eb2d6a35..beda3f71 100644 --- a/apps/social/src/app/(authentication)/register/page.tsx +++ b/apps/social/src/app/(authentication)/register/page.tsx @@ -45,7 +45,7 @@ export default async function Register() { } return ( -
+
diff --git a/apps/social/src/app/components/feed.tsx b/apps/social/src/app/components/feed.tsx new file mode 100644 index 00000000..83dbd6e5 --- /dev/null +++ b/apps/social/src/app/components/feed.tsx @@ -0,0 +1,42 @@ +import { PostCard } from "./post-card"; + +const data = [ + { + imageUrl: + "https://lh3.googleusercontent.com/a/ACg8ocK4CtuGuDZlPy9H_DMb3EQIue9Hrd5bqYcMZOY-Xb8LcuyqsBI=s96-c", + username: "umamin", + displayName: "Umamin Official", + createdAt: 1718604131, + content: + "An open-source social platform built exclusively for the Umamin community.", + isLiked: true, + isVerified: true, + likes: 24, + comments: 9, + }, + { + imageUrl: + "https://lh3.googleusercontent.com/a/ACg8ocJf40m8VVe3wNxhgBe11Bm7ukLSPeR0SDPPg6q8wq6NYRZtCYk=s96-c", + username: "josh", + displayName: "Josh Daniel", + createdAt: 1718342984, + content: + "We're building Umamin Social, a new platform to connect the community. Coming soon! 🚀", + isLiked: false, + isVerified: false, + likes: 7, + comments: 4, + }, +]; + +export function Feed() { + return ( +
+
+ {data.map((props) => ( + + ))} +
+
+ ); +} diff --git a/apps/social/src/app/components/navbar.tsx b/apps/social/src/app/components/navbar.tsx new file mode 100644 index 00000000..ae15038b --- /dev/null +++ b/apps/social/src/app/components/navbar.tsx @@ -0,0 +1,29 @@ +import Link from "next/link"; +import { logout } from "@/actions"; +import { getSession } from "@/lib/auth"; +import { SignOutButton } from "./sign-out-btn"; +import { Button } from "@umamin/ui/components/button"; + +export async function Navbar() { + const { session } = await getSession(); + + return ( + + ); +} diff --git a/apps/social/src/app/components/post-card.tsx b/apps/social/src/app/components/post-card.tsx new file mode 100644 index 00000000..51902ae7 --- /dev/null +++ b/apps/social/src/app/components/post-card.tsx @@ -0,0 +1,78 @@ +import Link from "next/link"; +import { + Avatar, + AvatarFallback, + AvatarImage, +} from "@umamin/ui/components/avatar"; +import { cn } from "@umamin/ui/lib/utils"; +import { shortTimeAgo } from "@/lib/utils"; +import { BadgeCheck, Heart, MessageCircle, ScanFace } from "lucide-react"; + +type Props = { + imageUrl: string; + username: string; + displayName: string; + createdAt: number; + content: string; + isLiked: boolean; + isVerified: boolean; + likes: number; + comments: number; +}; + +export function PostCard(props: Props) { + return ( +
+ + + + + + + +
+
+
+ + {props.displayName} + + + {props.isVerified && ( + + )} + @{props.username} +
+ +

+ {shortTimeAgo(props.createdAt)} +

+
+ +

{props.content}

+ +
+
+ + {props.likes} +
+ +
+ + {props.comments} +
+
+
+
+ ); +} diff --git a/apps/social/src/app/components/sign-out-btn.tsx b/apps/social/src/app/components/sign-out-btn.tsx new file mode 100644 index 00000000..d34486a3 --- /dev/null +++ b/apps/social/src/app/components/sign-out-btn.tsx @@ -0,0 +1,21 @@ +"use client"; + +import { Loader2 } from "lucide-react"; +import { useFormStatus } from "react-dom"; +import { Button } from "@umamin/ui/components/button"; + +export function SignOutButton() { + const { pending } = useFormStatus(); + + return ( + + ); +} diff --git a/apps/social/src/app/layout.tsx b/apps/social/src/app/layout.tsx index fa5d08a6..21f0181b 100644 --- a/apps/social/src/app/layout.tsx +++ b/apps/social/src/app/layout.tsx @@ -5,6 +5,7 @@ import { GeistSans } from "geist/font/sans"; import NextTopLoader from "nextjs-toploader"; import type { Metadata, Viewport } from "next"; +import { Navbar } from "./components/navbar"; import { ThemeProvider } from "@umamin/ui/components/theme-provider"; export const viewport: Viewport = { @@ -88,6 +89,7 @@ export default function RootLayout({ }, }} /> + {children} diff --git a/apps/social/src/app/page.tsx b/apps/social/src/app/page.tsx index c815d44e..77161ba0 100644 --- a/apps/social/src/app/page.tsx +++ b/apps/social/src/app/page.tsx @@ -1,20 +1,9 @@ -import { logout } from "@/actions"; -import { getSession } from "@/lib/auth"; -import { Button } from "@umamin/ui/components/button"; - -export default async function Home() { - const { session } = await getSession(); +import { Feed } from "./components/feed"; +export default function Home() { return (
-

Hello World

- {session && ( -
- -
- )} +
); } diff --git a/apps/social/src/lib/utils.ts b/apps/social/src/lib/utils.ts new file mode 100644 index 00000000..4166fd0e --- /dev/null +++ b/apps/social/src/lib/utils.ts @@ -0,0 +1,36 @@ +import { formatDistanceToNow, fromUnixTime } from "date-fns"; + +export function shortTimeAgo(epoch: number) { + const distance = formatDistanceToNow(fromUnixTime(epoch)); + + if (distance === "less than a minute") { + return "just now"; + } + + const minutesMatch = distance.match(/(\d+)\s+min/); + if (minutesMatch) { + return `${minutesMatch[1]}m`; + } + + const hoursMatch = distance.match(/(\d+)\s+hour/); + if (hoursMatch) { + return `${hoursMatch[1]}h`; + } + + const daysMatch = distance.match(/(\d+)\s+day/); + if (daysMatch) { + return `${daysMatch[1]}d`; + } + + const monthsMatch = distance.match(/(\d+)\s+month/); + if (monthsMatch) { + return `${monthsMatch[1]}mo`; + } + + const yearsMatch = distance.match(/(\d+)\s+year/); + if (yearsMatch) { + return `${yearsMatch[1]}y`; + } + + return distance; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7aa9afa3..b84d1fb1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -74,6 +74,9 @@ importers: arctic: specifier: ^1.8.1 version: 1.9.2 + date-fns: + specifier: ^3.6.0 + version: 3.6.0 geist: specifier: ^1.3.1 version: 1.3.1(next@14.2.5(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) @@ -4306,6 +4309,7 @@ packages: libsql@0.3.19: resolution: {integrity: sha512-Aj5cQ5uk/6fHdmeW0TiXK42FqUlwx7ytmMLPSaUQPin5HKKKuUPD62MAbN4OEweGBBI7q1BekoEN4gPUEL6MZA==} + cpu: [x64, arm64, wasm32] os: [darwin, linux, win32] lilconfig@2.1.0: @@ -9002,7 +9006,7 @@ snapshots: eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) eslint-plugin-react: 7.35.0(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) @@ -9056,7 +9060,7 @@ snapshots: enhanced-resolve: 5.17.1 eslint: 8.57.0 eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.6 is-core-module: 2.15.0 @@ -9131,7 +9135,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5