mirror of
https://github.com/BradNut/svelteKitForBeginners
synced 2025-09-08 17:40:24 +00:00
Adding settings pages with default text, about page that is static, and a form action fully typed used in all form elements. If JS exists then use fetch otherwise default to regular HTML form ways.
This commit is contained in:
parent
9d13216fea
commit
4324a70a7a
10 changed files with 253 additions and 3 deletions
|
|
@ -1,4 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { enhance } from '$root/lib/form'
|
||||||
|
|
||||||
let tweet = ''
|
let tweet = ''
|
||||||
let maxCharacters = 140
|
let maxCharacters = 140
|
||||||
|
|
||||||
|
|
@ -8,7 +10,14 @@
|
||||||
<div class="compose">
|
<div class="compose">
|
||||||
<img src="/profile/matia/avatar.webp" alt="Avatar">
|
<img src="/profile/matia/avatar.webp" alt="Avatar">
|
||||||
|
|
||||||
<form action="/home" method="post" autocomplete="off">
|
<form
|
||||||
|
action="/home"
|
||||||
|
method="post"
|
||||||
|
autocomplete="off"
|
||||||
|
use:enhance={{
|
||||||
|
result: ({ form }) => form.reset()
|
||||||
|
}}
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
aria-label="Enter your tweet"
|
aria-label="Enter your tweet"
|
||||||
bind:value={tweet}
|
bind:value={tweet}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { fade, fly } from 'svelte/transition'
|
import { fade, fly } from 'svelte/transition'
|
||||||
|
|
||||||
|
import { enhance } from '$root/lib/form'
|
||||||
import Icon from '$root/components/icon.svelte'
|
import Icon from '$root/components/icon.svelte'
|
||||||
import type { TweetType } from '$root/types'
|
import type { TweetType } from '$root/types'
|
||||||
|
|
||||||
|
|
@ -32,7 +33,11 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<form action="/home/like" method="post">
|
<form
|
||||||
|
action="/home/like"
|
||||||
|
method="post"
|
||||||
|
use:enhance
|
||||||
|
>
|
||||||
<input type="hidden" name="id" value={tweet.id} />
|
<input type="hidden" name="id" value={tweet.id} />
|
||||||
<button
|
<button
|
||||||
class="btn like"
|
class="btn like"
|
||||||
|
|
@ -72,7 +77,11 @@
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<form action="/home?_method=delete" method="post">
|
<form
|
||||||
|
action="/home?_method=delete"
|
||||||
|
method="post"
|
||||||
|
use:enhance
|
||||||
|
>
|
||||||
<input type="hidden" name="id" value={tweet.id} />
|
<input type="hidden" name="id" value={tweet.id} />
|
||||||
<button
|
<button
|
||||||
aria-label="Remove tweet"
|
aria-label="Remove tweet"
|
||||||
|
|
|
||||||
48
src/lib/form.ts
Normal file
48
src/lib/form.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { invalidate } from "$app/navigation"
|
||||||
|
import { page } from "$app/stores"
|
||||||
|
|
||||||
|
type Parameters = {
|
||||||
|
result?: ({ form }: { form: HTMLFormElement }) => void
|
||||||
|
}
|
||||||
|
type Destroy = { destroy: () => void }
|
||||||
|
type Enhance = (
|
||||||
|
form?: HTMLFormElement,
|
||||||
|
{ result }?: Parameters
|
||||||
|
) => Destroy
|
||||||
|
|
||||||
|
export const enhance: Enhance = (form, { result } = {}) => {
|
||||||
|
let invalidatePath: URL
|
||||||
|
|
||||||
|
page.subscribe((path) => {
|
||||||
|
invalidatePath = path.url
|
||||||
|
})
|
||||||
|
|
||||||
|
async function handleSubmit(event: Event) {
|
||||||
|
event.preventDefault()
|
||||||
|
const response = await fetch(form.action, {
|
||||||
|
method: form.method,
|
||||||
|
headers: { accept: 'application/json' },
|
||||||
|
body: new FormData(form)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(await response.text());
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(invalidatePath)
|
||||||
|
url.search = ''
|
||||||
|
url.hash = ''
|
||||||
|
invalidate(url.href)
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
result({ form })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
form.addEventListener('submit', handleSubmit)
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy() {
|
||||||
|
form.removeEventListener('submit', handleSubmit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/routes/home/about/index.svelte
Normal file
38
src/routes/home/about/index.svelte
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
<script context="module" lang="ts">
|
||||||
|
import { dev } from '$app/env'
|
||||||
|
|
||||||
|
export const hydrate = dev
|
||||||
|
export const prerender = true
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>About</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h1>About</h1>
|
||||||
|
<div class="content">
|
||||||
|
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Molestiae mollitia, dolores consequatur quos hic commodi eum eius ex et laudantium quia dolore quidem aliquid tempore sit. A natus dolor sed!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
font-size: var(--font-24);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--spacing-8);
|
||||||
|
padding: var(--spacing-16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--spacing-16);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
123
src/routes/home/settings/__layout.reset.svelte
Normal file
123
src/routes/home/settings/__layout.reset.svelte
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
import Navigation from '$root/components/navigation.svelte';
|
||||||
|
|
||||||
|
$: path = $page.url.pathname
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<Navigation />
|
||||||
|
|
||||||
|
<main class="settings">
|
||||||
|
<h1 class="title">Settings</h1>
|
||||||
|
|
||||||
|
<section class="privacy">
|
||||||
|
<h2>Privacy</h2>
|
||||||
|
<a
|
||||||
|
class:active={
|
||||||
|
path === '/home/settings/personalization'
|
||||||
|
}
|
||||||
|
href="/home/settings/personalization"
|
||||||
|
>
|
||||||
|
Personalization and data
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class:active={
|
||||||
|
path === '/home/settings/data'
|
||||||
|
}
|
||||||
|
href="/home/settings/data"
|
||||||
|
>
|
||||||
|
Your Twitttr data
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class:active={
|
||||||
|
path === '/home/settings/cookies'
|
||||||
|
}
|
||||||
|
href="/home/settings/cookies"
|
||||||
|
>
|
||||||
|
Cookie preferences
|
||||||
|
</a>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="general">
|
||||||
|
<h2>General</h2>
|
||||||
|
<a
|
||||||
|
class:active={
|
||||||
|
path === '/home/settings/resources'
|
||||||
|
}
|
||||||
|
href="/home/settings/resources"
|
||||||
|
>
|
||||||
|
Additional resources
|
||||||
|
</a>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<aside>
|
||||||
|
<slot />
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
height: 100vh;
|
||||||
|
max-width: min-content;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: min-content 50ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings {
|
||||||
|
display: grid;
|
||||||
|
grid-auto-rows: min-content;
|
||||||
|
gap: var(--spacing-24);
|
||||||
|
border: 1px solid var(--color-border-primary);
|
||||||
|
border-top: none;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.privacy,
|
||||||
|
.general {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: var(--font-18);
|
||||||
|
margin-top: var(--spacing-24);
|
||||||
|
padding: 0 var(--spacing-16);
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: var(--font-24);
|
||||||
|
padding: 0 var(--spacing-16);
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-bottom: var(--spacing-16);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
padding: 1.4rem var(--spacing-16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
background: var(--color-link-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
aside {
|
||||||
|
display: none;
|
||||||
|
padding: var(--spacing-16);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.container {
|
||||||
|
max-width: 1240px;
|
||||||
|
margin: 0 auto;
|
||||||
|
grid-template-columns: 1fr 50ch 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
aside {
|
||||||
|
width: 400px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
5
src/routes/home/settings/cookies.svelte
Normal file
5
src/routes/home/settings/cookies.svelte
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svelte:head>
|
||||||
|
<title>Settings | Cookies</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<h3>Cookie preferences</h3>
|
||||||
5
src/routes/home/settings/data.svelte
Normal file
5
src/routes/home/settings/data.svelte
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svelte:head>
|
||||||
|
<title>Settings | Data</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<h3>Your Twitter data</h3>
|
||||||
3
src/routes/home/settings/index.svelte
Normal file
3
src/routes/home/settings/index.svelte
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svelte:head>
|
||||||
|
<title>Settings</title>
|
||||||
|
</svelte:head>
|
||||||
5
src/routes/home/settings/personalization.svelte
Normal file
5
src/routes/home/settings/personalization.svelte
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svelte:head>
|
||||||
|
<title>Settings | Personalization</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<h3>Personalization and data</h3>
|
||||||
5
src/routes/home/settings/resources.svelte
Normal file
5
src/routes/home/settings/resources.svelte
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svelte:head>
|
||||||
|
<title>Settings | Resources</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<h3>Additional resources</h3>
|
||||||
Loading…
Reference in a new issue