Skip to content

Commit

Permalink
[Feature] Modify Links and Skills order / fix #15 #45
Browse files Browse the repository at this point in the history
[Feature] Modify Links and Skills order / fix #15
  • Loading branch information
s1lvax authored Oct 19, 2024
2 parents 65315b9 + 1ca9a1b commit 0c24fa4
Show file tree
Hide file tree
Showing 14 changed files with 198 additions and 70 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"prisma": "^5.20.0",
"svelte": "^4.2.7",
"svelte-check": "^4.0.0",
"svelte-dnd-action": "^0.9.51",
"sveltekit-superforms": "^2.19.1",
"tailwindcss": "^3.4.9",
"typescript": "^5.0.0",
Expand Down
2 changes: 2 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ model Link {
url String
postedBy User @relation(fields: [userId], references: [githubId])
userId Int
order Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Expand All @@ -40,6 +41,7 @@ model Skill {
level String
postedBy User @relation(fields: [userId], references: [githubId])
userId Int
order Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
9 changes: 9 additions & 0 deletions src/lib/components/MyProfile/LinkForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
import { zodClient } from 'sveltekit-superforms/adapters';
export let data: SuperValidated<Infer<LinksSchema>>;
export let linksLength: number;
const form = superForm(data, {
validators: zodClient(linksSchema)
});
const { form: formData, enhance } = form;
$: $formData.order = linksLength;
</script>

<form
Expand Down Expand Up @@ -40,5 +43,11 @@
</Form.Field>
</div>

<Form.Field {form} name="order">
<Form.Control let:attrs>
<Input {...attrs} bind:value={$formData.order} type="hidden" />
</Form.Control>
</Form.Field>

<Form.Button class="mt-5 flex align-bottom">Add</Form.Button>
</form>
8 changes: 8 additions & 0 deletions src/lib/components/MyProfile/SkillsForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { zodClient } from 'sveltekit-superforms/adapters';
export let data: SuperValidated<Infer<SkillsSchema>>;
export let skillsLength: number;
const form = superForm(data, {
validators: zodClient(skillsSchema),
Expand All @@ -21,6 +22,7 @@
const { form: formData, enhance } = form;
$: $formData.order = skillsLength;
$: selectedLevel = $formData.level
? {
label: $formData.level,
Expand Down Expand Up @@ -73,5 +75,11 @@
</Form.Field>
</div>

<Form.Field {form} name="order">
<Form.Control let:attrs>
<Input {...attrs} bind:value={$formData.order} type="hidden" />
</Form.Control>
</Form.Field>

<Form.Button>Add</Form.Button>
</form>
110 changes: 60 additions & 50 deletions src/lib/components/MyProfile/UserLinks.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,59 +4,69 @@
import * as Table from '$lib/components/ui/table';
import { formatDate } from '$lib/utils/formatDate';
import { Button } from '$lib/components/ui/button';
import { Trash2, Copy } from 'lucide-svelte';
import { Trash2, Copy, AlignJustify } from 'lucide-svelte';
import { copyToClipboard } from '$lib/utils/copyToClipboard';
import { confirmDelete } from '$lib/utils/confirmDelete';
import DnD from '$lib/components/Shared/DnD.svelte';
import type { Link } from '@prisma/client';
export let links; // Use the Link type for the links prop
export let links: Link[]; // Use the Link type for the links prop
let dragDisabled = false;
const handleDrop = async () => {
dragDisabled = true;
await fetch('/profile/links/order', {
method: 'PATCH',
body: JSON.stringify({ links }),
headers: {
'content-type': 'application/json',
},
});
dragDisabled = false;
}
</script>

<Card.Root>
<Card.Header>
<Card.Title>Links</Card.Title>
<Card.Description>The links visible on your profile</Card.Description>
</Card.Header>
<Card.Content>
<Table.Root>
<Table.Header>
<Table.Row>
<Table.Head>Title</Table.Head>
<Table.Head>URL</Table.Head>
<Table.Head>Created At</Table.Head>
<Table.Head>Actions</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#each links as link}
<Table.Row>
<Table.Cell>
<div class="font-medium">{link.title}</div>
</Table.Cell>
<Table.Cell>
<a
href={link.url}
target="_blank"
rel="noopener noreferrer"
class="text-blue-600 hover:underline">Link is available here</a
>
<Table.Root>
<Table.Header>
<Table.Row>
<Table.Head></Table.Head>
<Table.Head>Title</Table.Head>
<Table.Head>URL</Table.Head>
<Table.Head>Created At</Table.Head>
<Table.Head>Actions</Table.Head>
</Table.Row>
</Table.Header>
<DnD items={links} dndOptions={{ dragDisabled }} updateNewItems={(newLinks) => links = newLinks} containerTag="tbody" class="[&_tr:last-child]:border-0" onDrop={handleDrop}>
{#each links as link(link.id)}
<Table.Row>
<Table.Cell>
<AlignJustify />
</Table.Cell>
<Table.Cell>
<div class="font-medium">{link.title}</div>
</Table.Cell>
<Table.Cell>
<a
href={link.url}
target="_blank"
rel="noopener noreferrer"
class="text-blue-600 hover:underline">Link is available here</a
>

<Button variant="ghost" on:click={() => copyToClipboard(link.url)}>
<Copy
class="h-4 w-4 transform transition-transform duration-300 hover:scale-110 hover:cursor-pointer"
/>
</Button>
</Table.Cell>
<Table.Cell>{formatDate(link.createdAt)}</Table.Cell>
<Table.Cell>
<form action="?/deleteLink&id={link.id}" method="POST" use:enhance>
<Button type="submit" id="deleteLink" variant="ghost" on:click={confirmDelete}>
<Trash2 />
</Button>
</form>
</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
</Card.Content>
</Card.Root>
<Button variant="ghost" on:click={() => copyToClipboard(link.url)}>
<Copy
class="h-4 w-4 transform transition-transform duration-300 hover:scale-110 hover:cursor-pointer"
/>
</Button>
</Table.Cell>
<Table.Cell>{formatDate(link.createdAt)}</Table.Cell>
<Table.Cell>
<form action="?/deleteLink&id={link.id}" method="POST" use:enhance>
<Button type="submit" id="deleteLink" variant="ghost" on:click={confirmDelete}>
<Trash2 />
</Button>
</form>
</Table.Cell>
</Table.Row>
{/each}
</DnD>
</Table.Root>
35 changes: 28 additions & 7 deletions src/lib/components/MyProfile/UserSkills.svelte
Original file line number Diff line number Diff line change
@@ -1,17 +1,38 @@
<script lang="ts">
import { BriefcaseBusiness, Trash2 } from 'lucide-svelte';
export let skills;
import { BriefcaseBusiness, Trash2, AlignJustify } from 'lucide-svelte';
import { masteryLevels } from '$lib/constants/masteryLevel';
import { enhance } from '$app/forms';
import { Button } from '$lib/components/ui/button';
import { confirmDelete } from '$lib/utils/confirmDelete';
import DnD from '$lib/components/Shared/DnD.svelte';
import type { Skill } from '@prisma/client/wasm';
export let skills: Skill[];
let dragDisabled = false;
const handleDrop = async () => {
dragDisabled = true;
await fetch('/profile/skills/order', {
method: 'PATCH',
body: JSON.stringify({ skills }),
headers: {
'content-type': 'application/json'
}
});
dragDisabled = false;
}
</script>

<div class="grid gap-4">
{#each skills as skill}
<DnD
items={skills}
dndOptions={{ dragDisabled }}
updateNewItems={(newSkills) => (skills = newSkills)}
class="grid gap-4"
onDrop={handleDrop}
>
{#each skills as skill(skill.id)}
<div class="flex items-center gap-4">
<AlignJustify />
<BriefcaseBusiness />
<div class="grid gap-1">
<p class="text-sm font-medium leading-none">{skill.title}</p>
Expand All @@ -28,4 +49,4 @@
</div>
</div>
{/each}
</div>
</DnD>
24 changes: 24 additions & 0 deletions src/lib/components/Shared/DnD.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script lang="ts">
import { dndzone } from 'svelte-dnd-action';
import type { Options, DndEvent, Item } from 'svelte-dnd-action';
export let items: any[];
export let updateNewItems: (newItems: any[]) => void;
export let onDrop: ((e: CustomEvent<DndEvent<Item>>) => void) | undefined = undefined;
export let containerTag: string = 'div';
export let dndOptions: Omit<Options<Item>, 'items'|'type'> = {};
export let type: string = crypto.randomUUID(); // By default DnD will have different types, to prevent dragging between DnD zones.
const handleDndConsider = (e: CustomEvent<DndEvent<Item>>) => {
updateNewItems(e.detail.items)
}
const handleDndFinalize = (e: CustomEvent<DndEvent<Item>>) => {
updateNewItems(e.detail.items)
onDrop && onDrop(e);
}
</script>

<svelte:element this="{containerTag}" use:dndzone="{{ ...dndOptions, items, type }}" on:consider="{handleDndConsider}" on:finalize="{handleDndFinalize}" {...$$restProps}>
<slot></slot>
</svelte:element>
3 changes: 2 additions & 1 deletion src/lib/schemas/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ export const linksSchema = z.object({
.max(200)
.refine((val) => val.startsWith('http://') || val.startsWith('https://'), {
message: 'Oops! URLs usually start with http:// or https:// :P'
})
}),
order: z.number()
});

export type LinksSchema = typeof linksSchema;
3 changes: 2 additions & 1 deletion src/lib/schemas/skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { z } from 'zod';

export const skillsSchema = z.object({
title: z.string().min(1).max(50),
level: z.string()
level: z.string(),
order: z.number()
});

export type SkillsSchema = typeof skillsSchema;
6 changes: 4 additions & 2 deletions src/routes/[username]/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ export const load: PageServerLoad = async ({ params }) => {
});

const links = await prisma.link.findMany({
where: { userId: user.githubId }
where: { userId: user.githubId },
orderBy: [{ order: 'asc' }]
});

const skills = await prisma.skill.findMany({
where: { userId: user.githubId }
where: { userId: user.githubId },
orderBy: [{ order: 'asc' }]
});

const userData = {
Expand Down
16 changes: 10 additions & 6 deletions src/routes/profile/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,13 @@ export const load: PageServerLoad = async (event) => {

// Fetch links and skills related to the user
const links = await prisma.link.findMany({
where: { userId: user.githubId }
where: { userId: user.githubId },
orderBy: [{ order: 'asc' }]
});

const skills = await prisma.skill.findMany({
where: { userId: user.githubId }
where: { userId: user.githubId },
orderBy: [{ order: 'asc' }]
});

// Create userStats object
Expand Down Expand Up @@ -83,15 +85,16 @@ export const actions: Actions = {
}

// If no errors, get data
const { title, url } = form.data;
const { title, url, order } = form.data;

if (user) {
try {
await prisma.link.create({
data: {
title,
url,
userId: user.githubId
userId: user.githubId,
order
}
});
} catch (error) {
Expand Down Expand Up @@ -137,15 +140,16 @@ export const actions: Actions = {
}

// If no errors, get data
const { title, level } = form.data;
const { title, level, order } = form.data;

if (user) {
try {
await prisma.skill.create({
data: {
title,
level,
userId: user.githubId
userId: user.githubId,
order
}
});
} catch (error) {
Expand Down
9 changes: 6 additions & 3 deletions src/routes/profile/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,10 @@
<Card.Header class="flex flex-row items-center">
<div class="grid gap-2">
<Card.Title>Links</Card.Title>
<Card.Description>The links visible on your profile</Card.Description>
<LinkForm data={data.form} />
<Card.Description>
The links visible on your profile. You can drag links around to modify the order
</Card.Description>
<LinkForm data={data.form} linksLength={data.links.length} />
</div>
</Card.Header>
<Card.Content>
Expand All @@ -79,7 +81,8 @@
<Card.Root>
<Card.Header>
<Card.Title>Tech Stack</Card.Title>
<SkillsForm data={data.skillsForm} />
<Card.Description>You can drag skills around to modify the order</Card.Description>
<SkillsForm data={data.skillsForm} skillsLength={data.skills.length} />
</Card.Header>
<Card.Content class="grid gap-8">
<UserSkills skills={data.skills} />
Expand Down
Loading

0 comments on commit 0c24fa4

Please sign in to comment.