Initial commit, basic pages and sessions.

This commit is contained in:
2023-05-29 00:54:17 +02:00
commit bc9fec207b
57 changed files with 15967 additions and 0 deletions

View File

@@ -0,0 +1,177 @@
<template>
<div class="content">
<h2 class="heading">Create new account</h2>
<form
class="form create"
@submit.prevent="signup"
>
<div
v-if="errorMsg"
class="form-section form-error"
>{{ errorMsg }}</div>
<div class="form-section">
<div class="form-row">
<input
v-model="username"
placeholder="Username"
maxlength="32"
class="input"
>
</div>
<div class="form-row">
<input
v-model="email"
type="email"
placeholder="E-mail"
maxlength="500"
class="input"
>
</div>
<div class="form-row">
<input
v-model="password"
type="password"
minlength="8"
maxlength="500"
placeholder="Password"
class="input"
>
</div>
</div>
<div
v-if="config.captchaEnabled"
class="form-row captcha"
>
<VueHcaptcha
:sitekey="config.captchaKey"
@verify="(token) => captchaToken = token"
@expired="() => captchaToken = null"
@error="() => captchaToken = null"
/>
</div>
<div class="form-section">
<label class="check-container noselect">
<span>
<span class="description">I have read and accept the <a
href="/help/user-agreement"
target="_blank"
class="link"
>User Agreement</a></span>
</span>
<Checkbox
:checked="agreeTos"
@change="(checked) => agreeTos = checked"
/>
</label>
</div>
<div class="form-row form-actions">
<button
:disabled="!username || !email || !password || !agreeTos || (config.captchaEnabled && !captchaToken)"
class="button button-submit"
>Sign up</button>
</div>
</form>
</div>
</template>
<script setup>
import { ref } from 'vue';
import VueHcaptcha from '@hcaptcha/vue3-hcaptcha';
import Checkbox from '../../components/form/checkbox.vue';
import navigate from '../../assets/js/navigate';
import { post } from '../../assets/js/api';
const config = CONFIG;
const username = ref('');
const email = ref('');
const password = ref('');
const agreeTos = ref(false);
const captchaToken = ref(null);
const errorMsg = ref(null);
async function signup() {
try {
await post('/api/users', {
username: username.value,
email: email.value,
password: password.value,
captchaToken: captchaToken.value,
});
navigate('/account/login');
} catch (error) {
errorMsg.value = error.message;
}
}
</script>
<style scoped>
.content {
display: flex;
justify-content: center;
align-items: center;
}
.create {
background: var(--background);
padding: 1rem;
border-radius: .5rem;
}
.name {
position: relative;
.input {
padding-left: 1.65rem;
}
.prefix {
color: var(--shadow);
position: absolute;
top: 1px;
left: .25rem;
letter-spacing: .1rem;
padding: .5rem;
}
}
.access {
font-weight: bold;
margin-bottom: .75rem;
cursor: pointer;
}
.access-description {
margin: .25rem 0 0 1.25rem;
color: var(--shadow);
font-size: .9rem;
font-weight: normal;
}
.nsfw {
margin-right: .5rem;
font-weight: bold;
color: var(--error);
}
.captcha {
justify-content: center;
}
.form-actions {
justify-content: center;
}
</style>

View File

@@ -0,0 +1,142 @@
<template>
<div class="content">
<h2 class="heading">Log in</h2>
<form
class="form create"
@submit.prevent="signup"
>
<div
v-if="errorMsg"
class="form-section form-error"
>{{ errorMsg }}</div>
<div class="form-section">
<div class="form-row">
<input
v-model="username"
placeholder="Username or e-mail"
maxlength="500"
class="input"
>
</div>
<div class="form-row">
<input
v-model="password"
type="password"
minlength="8"
maxlength="500"
placeholder="Password"
class="input"
>
</div>
</div>
<!--
<div
v-if="$config.public.captchaEnabled"
class="form-row captcha"
>
<VueHcaptcha
:sitekey="$config.public.captchaKey"
@verify="(token) => captchaToken = token"
@expired="() => captchaToken = null"
@error="() => captchaToken = null"
/>
</div>
-->
<div class="form-row form-actions">
<button
:disabled="!username || !password"
class="button button-submit"
>Log in</button>
</div>
</form>
</div>
</template>
<script setup>
import { ref } from 'vue';
// import VueHcaptcha from '@hcaptcha/vue3-hcaptcha';
import { post } from '../../assets/js/api';
import navigate from '../../assets/js/navigate';
const username = ref('');
const password = ref('');
const errorMsg = ref(null);
async function signup() {
try {
await post('/api/session', {
username: username.value,
password: password.value,
});
navigate('/');
} catch (error) {
errorMsg.value = error.statusMessage;
}
}
</script>
<style scoped>
.content {
display: flex;
justify-content: center;
align-items: center;
}
.create {
background: var(--background);
padding: 1rem;
border-radius: .5rem;
}
.name {
position: relative;
.input {
padding-left: 1.65rem;
}
.prefix {
color: var(--shadow);
position: absolute;
top: 1px;
left: .25rem;
letter-spacing: .1rem;
padding: .5rem;
}
}
.access {
font-weight: bold;
margin-bottom: .75rem;
cursor: pointer;
}
.access-description {
margin: .25rem 0 0 1.25rem;
color: var(--shadow);
font-size: .9rem;
font-weight: normal;
}
.nsfw {
margin-right: .5rem;
font-weight: bold;
color: var(--error);
}
.captcha {
justify-content: center;
}
.form-actions {
justify-content: center;
}
</style>

View File

@@ -0,0 +1,7 @@
# User Agreement
## Access
You must be over the age of 13 to access Shack, with or without account. Communities (shelves) labeled `NSFW` or `18+` indicate the presence of explicit content such as pornography or (simulated) violence, and may only be viewed if you are over the age of 18, you are legally permitted to view explicit content in your jurisdiction, and you do not consider explicit content as obscene or offensive.
## Privacy
Shack may collect information about your device and connection, such as your IP address, operating system and browser configuration, for the purpose of moderation and internal analytics. This information is not shared with third parties or used for targeted advertising.

8
pages/index/Counter.vue Normal file
View File

@@ -0,0 +1,8 @@
<template>
<button type="button" @click="state.count++">Counter {{ state.count }}</button>
</template>
<script setup>
import { reactive } from 'vue'
const state = reactive({ count: 0 })
</script>

View File

@@ -0,0 +1,25 @@
<template>
<div class="content">
<ul>
<li><a
href="/shelf/1"
class="link"
>Go to shelf</a></li>
<li><a
href="/shelf/create"
class="link"
>Create new shelf</a></li>
<li><a
href="/account/login"
class="link"
>Log in</a></li>
<li><a
href="/account/create"
class="link"
>Sign up</a></li>
</ul>
</div>
</template>

View File

@@ -0,0 +1,12 @@
<template>
<h1>Welcome</h1>
This page is:
<ul>
<li>Rendered to HTML.</li>
<li>Interactive. <Counter /></li>
</ul>
</template>
<script setup>
import Counter from './Counter.vue'
</script>

View File

@@ -0,0 +1,71 @@
<template>
<div class="content">
<a
href="/"
class="link"
>Go back home</a>
<h3>{{ shuck }}</h3>
<form
class="form compose"
@submit.prevent="submitPost"
>
<div class="form-row">
<input
v-model="title"
placeholder="Title"
class="input"
>
</div>
<div class="form-row">
<input
v-model="link"
class="input"
placeholder="Link"
>
</div>
<div class="form-row">
<textarea
v-model="body"
placeholder="Body"
class="input body"
/>
</div>
<div class="form-actions">
<button class="button button-submit">Post</button>
</div>
</form>
</div>
</template>
<script setup>
import { ref } from 'vue';
const shuck = 'Eratic';
// const route = useRoute();
// const res = await useFetch(`/api/shucks/${route.params.id}/posts`);
const title = ref();
const link = ref();
const body = ref();
async function submitPost() {
console.log('POST', link.value);
/*
await useFetch(`/api/shucks/${route.params.id}/posts`, {
method: 'post',
body: {
title,
link,
body,
},
});
*/
}
</script>

201
pages/shelf/create.page.vue Normal file
View File

@@ -0,0 +1,201 @@
<template>
<div class="content">
<h2 class="heading">Create new shelf</h2>
<form
class="form create"
@submit.prevent="create"
>
<div class="form-section">
<div class="form-row name">
<span class="prefix">s/</span>
<input
v-model="slug"
placeholder="home"
class="input"
>
</div>
<div class="form-row">
<input
v-model="title"
placeholder="Tag line"
class="input"
>
</div>
<div class="form-row">
<textarea
v-model="description"
placeholder="Description"
class="input"
/>
</div>
</div>
<div class="form-section">
<div class="form-row">
<div class="form-column">
<h4 class="form-heading">View access</h4>
<label class="access">
<input
v-model="viewAccess"
type="radio"
value="public"
class="radio"
>Public
<div class="access-description">Everyone can browse this shelf</div>
</label>
<label class="access">
<input
v-model="viewAccess"
type="radio"
value="registered"
class="radio"
>Registered
<div class="access-description">Registered shack users can browse</div>
</label>
<label class="access">
<input
v-model="viewAccess"
type="radio"
value="private"
class="radio"
>Private
<div class="access-description">Only invited users can browse</div>
</label>
</div>
<div class="form-column">
<h4 class="form-heading">Post access</h4>
<label class="access">
<input
v-model="postAccess"
type="radio"
value="registered"
class="radio"
>Registered
<div class="access-description">Registered users can post</div>
</label>
<label class="access">
<input
v-model="postAccess"
type="radio"
value="private"
class="radio"
>Private
<div class="access-description">Only invited users can post</div>
</label>
</div>
</div>
</div>
<div class="form-section">
<label class="check-container">
<span>
<span class="nsfw">NSFW 18+</span>
<span class="description">This community allows explicit content</span>
</span>
<Checkbox
:checked="isNsfw"
@change="(checked) => isNsfw = checked"
/>
</label>
</div>
<div class="form-row form-actions">
<button class="button button-submit">Create shelf</button>
</div>
</form>
</div>
</template>
<script setup>
import { ref } from 'vue';
import Checkbox from '../../components/form/checkbox.vue';
import { post } from '../../assets/js/api';
const slug = ref();
const title = ref();
const description = ref();
const viewAccess = ref('public');
const postAccess = ref('registered');
const isNsfw = ref(false);
async function create() {
await post('/api/shelves', {
slug: slug.value,
title: title.value,
description: description.value,
settings: {
viewAccess: viewAccess.value,
postAccess: postAccess.value,
isNsfw: isNsfw.value,
},
});
}
</script>
<style scoped>
.content {
display: flex;
justify-content: center;
align-items: center;
}
.create {
background: var(--background);
padding: 1rem;
border-radius: .5rem;
}
.name {
position: relative;
.input {
padding-left: 1.65rem;
}
.prefix {
color: var(--shadow);
position: absolute;
top: 1px;
left: .25rem;
letter-spacing: .1rem;
padding: .5rem;
}
}
.access {
font-weight: bold;
margin-bottom: .75rem;
cursor: pointer;
}
.access-description {
margin: .25rem 0 0 1.25rem;
color: var(--shadow);
font-size: .9rem;
font-weight: normal;
}
.nsfw {
margin-right: .5rem;
font-weight: bold;
color: var(--error);
}
</style>