feat: initial uploads and single study space view
Co-Authored-By: Alex Ling <al443@ic.ac.uk>
This commit is contained in:
118
src/routes/space/+page.svelte
Normal file
118
src/routes/space/+page.svelte
Normal file
@@ -0,0 +1,118 @@
|
||||
<script lang="ts">
|
||||
import Text from "$lib/components/inputs/Text.svelte";
|
||||
import Textarea from "$lib/components/inputs/Textarea.svelte";
|
||||
import Navbar from "$lib/components/Navbar.svelte";
|
||||
import crossUrl from "$lib/assets/cross.svg";
|
||||
import Button from "$lib/components/Button.svelte";
|
||||
import Image from "$lib/components/inputs/Image.svelte";
|
||||
import type { Table } from "$lib";
|
||||
import { goto, invalidate } from "$app/navigation";
|
||||
|
||||
const { data } = $props();
|
||||
const { supabase } = $derived(data);
|
||||
|
||||
let spaceImg = $state<FileList>();
|
||||
let studySpaceData = $state<Omit<Table<"study_spaces">, "id" | "created_at" | "updated_at">>({
|
||||
description: "",
|
||||
building_address: "",
|
||||
location: ""
|
||||
});
|
||||
|
||||
async function uploadStudySpace() {
|
||||
const imageFile = spaceImg?.[0];
|
||||
if (!imageFile) return alert("Please select an image file.");
|
||||
|
||||
const { data: studySpaceInsert, error: studySpaceError } = await supabase
|
||||
.from("study_spaces")
|
||||
.insert(studySpaceData)
|
||||
.select()
|
||||
.single();
|
||||
if (studySpaceError)
|
||||
return alert(`Error uploading study space: ${studySpaceError.message}`);
|
||||
|
||||
const { data: imgUpload, error: imageError } = await supabase.storage
|
||||
.from("files_bucket")
|
||||
.upload(`public/${studySpaceInsert.id}-${imageFile.name}`, imageFile, {
|
||||
contentType: imageFile.type
|
||||
});
|
||||
if (imageError) return alert(`Error uploading image: ${imageError.message}`);
|
||||
|
||||
const { error: imageInsertError } = await supabase
|
||||
.from("study_space_images")
|
||||
.insert({
|
||||
study_space_id: studySpaceInsert.id,
|
||||
image_path: imgUpload.path
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
if (imageInsertError) return alert(`Error creating image: ${imageInsertError.message}`);
|
||||
|
||||
alert("Thank you for your contribution!");
|
||||
// Redirect to the new study space page
|
||||
await goto(`/space/${studySpaceInsert.id}`, {
|
||||
invalidate: ["db:study_spaces"]
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<Navbar>
|
||||
<a href="/">
|
||||
<img src={crossUrl} alt="close" />
|
||||
</a>
|
||||
</Navbar>
|
||||
|
||||
<form
|
||||
onsubmit={async (event) => {
|
||||
event.preventDefault();
|
||||
await uploadStudySpace();
|
||||
}}
|
||||
>
|
||||
<Image name="study-space-image" minHeight="16rem" bind:files={spaceImg} />
|
||||
|
||||
<label for="location">Enter the name:</label>
|
||||
<Text name="location" bind:value={studySpaceData.location} placeholder="Room 123, Floor 1" />
|
||||
|
||||
<label for="description">Add a description:</label>
|
||||
<Textarea
|
||||
name="description"
|
||||
bind:value={studySpaceData.description}
|
||||
placeholder="A quiet, but small study space..."
|
||||
rows={5}
|
||||
/>
|
||||
|
||||
<label for="address">Add an address:</label>
|
||||
<Text
|
||||
name="address"
|
||||
bind:value={studySpaceData.building_address}
|
||||
placeholder="180 Queen's Gate, London, SW7 5HF"
|
||||
/>
|
||||
|
||||
<div class="submit">
|
||||
<Button type="submit">Share this study space!</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<style>
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1.5rem;
|
||||
gap: 0.5rem;
|
||||
max-width: 32rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
label {
|
||||
color: #ffffff;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
.submit {
|
||||
position: sticky;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 0.5rem;
|
||||
bottom: 0;
|
||||
margin-left: -0.5rem;
|
||||
width: calc(100% + 1rem);
|
||||
}
|
||||
</style>
|
||||
13
src/routes/space/[id]/+page.server.ts
Normal file
13
src/routes/space/[id]/+page.server.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { error } from "@sveltejs/kit";
|
||||
import type { PageServerLoad } from "./$types";
|
||||
|
||||
export const load: PageServerLoad = async ({ params, locals: { supabase } }) => {
|
||||
const { data: space, error: err } = await supabase
|
||||
.from("study_spaces")
|
||||
.select("*, study_space_images(*)")
|
||||
.eq("id", params.id)
|
||||
.single();
|
||||
if (err) error(500, "Failed to load study space");
|
||||
|
||||
return { space };
|
||||
};
|
||||
97
src/routes/space/[id]/+page.svelte
Normal file
97
src/routes/space/[id]/+page.svelte
Normal file
@@ -0,0 +1,97 @@
|
||||
<script lang="ts">
|
||||
import Navbar from "$lib/components/Navbar.svelte";
|
||||
import crossUrl from "$lib/assets/cross.svg";
|
||||
import placeholder from "$lib/assets/study_space.png";
|
||||
|
||||
const { data } = $props();
|
||||
const { space, supabase } = $derived(data);
|
||||
|
||||
const imgUrl = $derived(
|
||||
space.study_space_images.length > 0
|
||||
? supabase.storage
|
||||
.from("files_bucket")
|
||||
.getPublicUrl(space.study_space_images[0].image_path).data.publicUrl
|
||||
: placeholder
|
||||
);
|
||||
</script>
|
||||
|
||||
<Navbar>
|
||||
<a href="/">
|
||||
<img src={crossUrl} alt="close" />
|
||||
</a>
|
||||
</Navbar>
|
||||
|
||||
<main>
|
||||
<img src={imgUrl} alt="the study space" />
|
||||
<div class="nameContainer">
|
||||
{space.location}
|
||||
</div>
|
||||
<p class="descContainer">
|
||||
{space.description}
|
||||
</p>
|
||||
<hr />
|
||||
<div class="whereSubtitle">Where it is:</div>
|
||||
<p class="addrContainer">
|
||||
{space.building_address}
|
||||
</p>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 0 1rem 0;
|
||||
max-width: 32rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
aspect-ratio: 1/0.8;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
hr {
|
||||
height: 2px;
|
||||
background-color: #2e3c42;
|
||||
width: 70%;
|
||||
border: none;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.nameContainer {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.6rem;
|
||||
margin-top: -0.5rem;
|
||||
object-position: center;
|
||||
background-color: #49bd85;
|
||||
border-radius: 8px;
|
||||
font-size: 2.8rem;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.descContainer {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 1.4rem;
|
||||
margin-top: -0.5rem;
|
||||
object-position: center;
|
||||
border-radius: 8px;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.whereSubtitle {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
padding: 1.4rem;
|
||||
}
|
||||
|
||||
.addrContainer {
|
||||
font-size: 1.2rem;
|
||||
padding: 0rem 1.4rem;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user