Fetch tweets in an api and design how it shows up in the feed.

This commit is contained in:
Bradley Shellnut 2022-04-04 17:29:04 -07:00
parent cf52758c94
commit 6aebae4688
8 changed files with 351 additions and 1 deletions

219
src/components/tweet.svelte Normal file
View file

@ -0,0 +1,219 @@
<script lang="ts">
import { fade, fly } from 'svelte/transition'
import Icon from '$root/components/icon.svelte'
import type { TweetType } from '$root/types'
export let tweet: TweetType
</script>
<article class="tweet-container" transition:fade>
<a class="avatar" href="/home/profile/{tweet.name}">
<img
width="140"
height="140"
src={tweet.avatar}
alt={tweet.name}
/>
</a>
<div class="tweet-details">
<div>
<a href="/home/profile/{tweet.name}" class="user">
{tweet.name}
</a>
<span class="handle">{tweet.handle}</span>
<span class="posted"> · {tweet.posted}</span>
</div>
<div class="tweet">
<div class="content">
{tweet.content}
</div>
<div class="actions">
<form action="/home/like" method="post">
<input type="hidden" name="id" value={tweet.id} />
<button
class="btn like"
title="Like"
type="submit"
>
<div class="circle">
<Icon
width="24"
height="24"
name="like"
class={tweet.liked ? 'liked' : ''}
/>
</div>
<span class="count">
{#key tweet.likes}
{#if tweet.likes}
<div
in:fly={{ y: 40 }}
out:fly={{ y: 40 }}
>
{tweet.likes}
</div>
{/if}
{/key}
</span>
</button>
</form>
<a
href="/home/profile/{tweet.name}/status/{tweet.url}"
class="permalink"
title="Permalink"
>
<div class="circle">
<Icon width="24" height="24" name="permalink" />
</div>
</a>
<form action="/home?_method=delete" method="post">
<input type="hidden" name="id" value={tweet.id} />
<button
aria-label="Remove tweet"
class="btn remove"
title="Remove"
type="submit"
>
<div class="circle">
<Icon width="24" height="24" name="remove" />
</div>
</button>
</form>
</div>
</div>
</div>
</article>
<style>
.avatar {
align-self: start;
}
img {
width: 48px;
height: 48px;
border-radius: 50%;
}
button {
font-size: var(--font-16);
padding: var(--spacing-16);
}
.tweet-container:hover {
background-color: var(--color-bg-secondary);
}
.tweet-container {
display: grid;
grid-template-columns: min-content 1fr;
gap: var(--spacing-16);
padding: var(--spacing-16) var(--spacing-24);
transition: all 0.3s;
}
.tweet-container:not(:last-child) {
border-bottom: 1px solid var(--color-border-primary);
}
.tweet-details {
display: grid;
gap: var(--spacing-8);
}
.user {
font-weight: 700;
text-transform: capitalize;
}
.user:hover {
text-decoration: underline;
}
.handle,
.posted {
font-size: var(--font-16);
color: var(--color-text-muted);
}
.content {
font-size: var(--font-16);
}
.actions {
display: flex;
gap: var(--spacing-32);
margin-top: var(--spacing-16);
}
.actions button,
.actions a {
padding: 0;
color: var(--color-text-muted);
background: none;
transition: all 0.3s;
}
.circle {
width: 24px;
height: 24px;
display: grid;
place-content: center;
padding: var(--spacing-16);
border-radius: 50%;
transition: all 0.3s;
}
.circle > :global(.liked) {
color: hsl(9 100% 64%);
fill: hsl(9 100% 64%);
}
.like {
display: flex;
align-items: center;
}
.like:hover {
color: hsl(9 100% 64%);
}
.like:hover .circle {
background: hsla(9 100% 64% / 10%);
}
.permalink:hover {
color: hsl(120 100% 40%);
}
.permalink:hover .circle {
background-color: hsla(120 100% 50% / 4%);
}
.remove:hover {
color: hsl(0 100% 50%);
}
.remove:hover .circle {
background-color: hsla(0 100% 50% / 4%);
}
.like,
.remove,
.permalink {
width: 80px;
}
.count {
margin-left: var(--spacing-16);
font-size: 1.4rem;
font-weight: 400;
overflow: hidden;
}
</style>

24
src/lib/date.ts Normal file
View file

@ -0,0 +1,24 @@
export function timePosted(createdAt: Date): string {
try {
const posted = new Date(createdAt).getTime()
const currentTime = new Date().getTime()
const difference = currentTime - posted
const seconds = difference / 1000
const minutes = seconds / 60
const hours = minutes / 60
if (minutes <= 60) {
return `${minutes.toFixed()}m`
}
if (hours <= 24) {
return `${hours.toFixed()}h`
}
return Intl.DateTimeFormat('en-US', {
dateStyle: 'medium'
}).format(posted)
} catch (error) {
throw new Error(`💩 Something went wrong: ${error}`)
}
}

5
src/lib/prisma.ts Normal file
View file

@ -0,0 +1,5 @@
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient()
export default prisma

View file

@ -0,0 +1,10 @@
<script>
export let item
</script>
<h1>{item}</h1>
<form action="/api" method="post">
<input type="text" name="item" />
<button class="btn" type="submit">Submit</button>
</form>

27
src/routes/api/index.ts Normal file
View file

@ -0,0 +1,27 @@
let item = 'banana'
export function get() {
return {
status: 200,
headers: {
'Content-Type': 'application/json'
},
body: {
item
}
}
}
export async function post({ request }) {
const form = await request.formData()
const newItem = form.get('item')
item = newItem;
return {
status: 303,
headers: {
location: '/api'
}
}
}

View file

@ -1,9 +1,20 @@
<script lang="ts">
import Tweet from '$root/components/tweet.svelte'
import type { TweetType } from '$root/types'
export let tweets: TweetType[] = []
</script>
<svelte:head>
<title>Home</title>
<title>Home</title>
</svelte:head>
<h1>Feed</h1>
{#each tweets as tweet (tweet.id)}
<Tweet {tweet} />
{/each}
<style>
h1 {
position: sticky;

43
src/routes/home/index.ts Normal file
View file

@ -0,0 +1,43 @@
import type { RequestHandler } from "@sveltejs/kit";
import prisma from "$root/lib/prisma";
import { timePosted } from "$root/lib/date";
export const get: RequestHandler = async () => {
const data = await prisma.tweet.findMany({
include: { user: true },
orderBy: { posted: 'desc' }
})
const liked = await prisma.liked.findMany({
where: { userId: 1 },
select: { tweetId: true }
})
const likedTweets = Object.keys(liked).map(
key => liked[key].tweetId
)
const tweets = data.map(tweet => {
return {
id: tweet.id,
content: tweet.content,
likes: tweet.likes,
posted: timePosted(tweet.posted),
url: tweet.url,
avatar: tweet.user.avatar,
handle: tweet.user.handle,
name: tweet.user.name,
liked: likedTweets.includes(tweet.id)
}
})
if (!tweets) {
return { status: 400 }
}
return {
headers: { 'Content-Type': 'application/json ' },
status: 200,
body: { tweets }
}
}

11
src/types/index.ts Normal file
View file

@ -0,0 +1,11 @@
export type TweetType = {
id: number
content: string
likes: number
posted: string
url: string
avatar: string
handle: string
name: string
liked: boolean
}