This commit is contained in:
parent
943db3f29f
commit
827809c514
@ -1,4 +1,5 @@
|
|||||||
// api.ts
|
import type { Form, Submission, LoginCredentials } from './types';
|
||||||
|
|
||||||
const API_BASE_URL = 'http://127.0.0.1:8080';
|
const API_BASE_URL = 'http://127.0.0.1:8080';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,7 +28,7 @@ export async function createForm(name: string, fields: unknown): Promise<string>
|
|||||||
* Get all forms.
|
* Get all forms.
|
||||||
* @returns An array of forms.
|
* @returns An array of forms.
|
||||||
*/
|
*/
|
||||||
export async function getForms(): Promise<unknown[]> {
|
export async function getForms(): Promise<Form[]> {
|
||||||
const response = await fetch(`${API_BASE_URL}/forms`, {
|
const response = await fetch(`${API_BASE_URL}/forms`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
@ -69,7 +70,7 @@ export async function submitForm(formId: string, data: unknown): Promise<string>
|
|||||||
* @param formId The ID of the form.
|
* @param formId The ID of the form.
|
||||||
* @returns An array of submissions for the form.
|
* @returns An array of submissions for the form.
|
||||||
*/
|
*/
|
||||||
export async function getSubmissions(formId: string): Promise<unknown[]> {
|
export async function getSubmissions(formId: string): Promise<Submission[]> {
|
||||||
const response = await fetch(`${API_BASE_URL}/forms/${formId}/submissions`, {
|
const response = await fetch(`${API_BASE_URL}/forms/${formId}/submissions`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
@ -83,3 +84,47 @@ export async function getSubmissions(formId: string): Promise<unknown[]> {
|
|||||||
|
|
||||||
return await response.json();
|
return await response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Admin login to get a token.
|
||||||
|
* @param credentials The login credentials (username and password).
|
||||||
|
* @returns The generated JWT token if successful.
|
||||||
|
*/
|
||||||
|
export async function adminLogin(credentials: LoginCredentials): Promise<string> {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/admin/login`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(credentials)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Error during admin login: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data.token; // Assuming the response contains the token
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new admin user.
|
||||||
|
* @param user The login credentials for the admin user.
|
||||||
|
* @returns A success message upon creation.
|
||||||
|
*/
|
||||||
|
export async function createAdmin(user: LoginCredentials): Promise<string> {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/admin/create`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(user)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Error creating admin user: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data.message; // Assuming the response contains a success message
|
||||||
|
}
|
||||||
|
28
frontend/src/lib/session.svelte.ts
Normal file
28
frontend/src/lib/session.svelte.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import type { AdminUser } from './types';
|
||||||
|
|
||||||
|
const key = 'user';
|
||||||
|
const key2 = 'username';
|
||||||
|
|
||||||
|
function login(user: AdminUser) {
|
||||||
|
localStorage.setItem(key, btoa(`${user.username}:${user.password_hash}`));
|
||||||
|
localStorage.setItem(key2, user.username);
|
||||||
|
}
|
||||||
|
|
||||||
|
function logout() {
|
||||||
|
localStorage.removeItem(key);
|
||||||
|
localStorage.removeItem(key2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loggedIn() {
|
||||||
|
return localStorage.getItem(key) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function name() {
|
||||||
|
return localStorage.getItem(key2) ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function auth() {
|
||||||
|
return localStorage.getItem(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { login, logout, loggedIn, name, auth };
|
@ -17,3 +17,13 @@ export interface Submission {
|
|||||||
data: Record<string, unknown>;
|
data: Record<string, unknown>;
|
||||||
created_at?: string;
|
created_at?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LoginCredentials {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminUser {
|
||||||
|
username: string;
|
||||||
|
password_hash: string;
|
||||||
|
}
|
||||||
|
8
frontend/src/routes/(auth)/+layout.ts
Normal file
8
frontend/src/routes/(auth)/+layout.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import session from "$lib/session.svelte";
|
||||||
|
import { redirect } from "@sveltejs/kit";
|
||||||
|
|
||||||
|
export async function load() {
|
||||||
|
if (!session.loggedIn()) {
|
||||||
|
redirect(307, "/login");
|
||||||
|
}
|
||||||
|
}
|
149
frontend/src/routes/(auth)/create/+page.svelte
Normal file
149
frontend/src/routes/(auth)/create/+page.svelte
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { createForm } from '../../../lib/api';
|
||||||
|
import type { FormField } from '../../../lib/types';
|
||||||
|
|
||||||
|
let name = '';
|
||||||
|
let fields: FormField[] = [];
|
||||||
|
|
||||||
|
function addField() {
|
||||||
|
fields = [...fields, { label: '', name: '', field_type: 'text' }];
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeField(index: number) {
|
||||||
|
fields = fields.filter((_, i) => i !== index);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveForm() {
|
||||||
|
try {
|
||||||
|
await createForm(name, fields);
|
||||||
|
alert('Form created successfully!');
|
||||||
|
location.href = '/';
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to create form:', error);
|
||||||
|
alert('An error occurred while creating the form.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h1>Create Form</h1>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="form-name">Form Name</label>
|
||||||
|
<input id="form-name" type="text" bind:value={name} placeholder="Enter form name" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Fields</h2>
|
||||||
|
{#each fields as field, i}
|
||||||
|
<div class="field-container">
|
||||||
|
<label>
|
||||||
|
Field Label
|
||||||
|
<input type="text" bind:value={field.label} placeholder="Enter field label" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Field Type
|
||||||
|
<select bind:value={field.field_type}>
|
||||||
|
<option value="text">Text</option>
|
||||||
|
<option value="number">Number</option>
|
||||||
|
<option value="date">Date</option>
|
||||||
|
<option value="textarea">Textarea</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<button class="button remove-button" on:click={() => removeField(i)}> Remove </button>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<button class="button" on:click={addField}> Add Field </button>
|
||||||
|
<button class="button" on:click={saveForm} disabled={!name || fields.length === 0}>
|
||||||
|
Save Form
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2 {
|
||||||
|
color: #2c3e50;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-container {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus,
|
||||||
|
select:focus,
|
||||||
|
textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #42b983;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
background-color: #42b983;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 0.8rem 1.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:hover:not(:disabled) {
|
||||||
|
background-color: #3aa876;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:disabled {
|
||||||
|
background-color: #ccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-button {
|
||||||
|
background-color: #dc3545;
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-button:hover {
|
||||||
|
background-color: #c82333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
</style>
|
179
frontend/src/routes/(auth)/form/[id]/+page.svelte
Normal file
179
frontend/src/routes/(auth)/form/[id]/+page.svelte
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { getForms, getSubmissions, submitForm } from '../../../lib/api';
|
||||||
|
import type { Form, Submission } from '../../../lib/types';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
|
export let params: { id: string };
|
||||||
|
|
||||||
|
let form: Form | null = null;
|
||||||
|
let submissions: Submission[] = [];
|
||||||
|
let responseData: Record<string, any> = {};
|
||||||
|
let isSubmitting = false;
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
const { id } = $page.params;
|
||||||
|
if (id) {
|
||||||
|
form = await getForms().then((forms) => forms.find((f: Form) => f.id === id) || null);
|
||||||
|
submissions = await getSubmissions(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function submitResponse() {
|
||||||
|
if (isSubmitting) return;
|
||||||
|
|
||||||
|
isSubmitting = true;
|
||||||
|
try {
|
||||||
|
const { id } = $page.params;
|
||||||
|
await submitForm(id, responseData);
|
||||||
|
submissions = await getSubmissions(params.id);
|
||||||
|
responseData = {};
|
||||||
|
alert('Response submitted successfully!');
|
||||||
|
} catch (error) {
|
||||||
|
alert('Failed to submit response. Please try again.');
|
||||||
|
} finally {
|
||||||
|
isSubmitting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatSubmissionData(data: Record<string, any>): string {
|
||||||
|
return Object.entries(data)
|
||||||
|
.map(([key, value]) => `${key}: ${value}`)
|
||||||
|
.join(' | ');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
{#if form}
|
||||||
|
<h1>{form.name}</h1>
|
||||||
|
<!-- <div class="form-container">
|
||||||
|
<form on:submit|preventDefault={submitResponse}>
|
||||||
|
{#each form.fields as field}
|
||||||
|
<div class="field-group">
|
||||||
|
<label for={field.name}>{field.label}</label>
|
||||||
|
{#if field.field_type === 'textarea'}
|
||||||
|
<textarea id={field.name} bind:value={responseData[field.name]}></textarea>
|
||||||
|
{:else}
|
||||||
|
<input
|
||||||
|
id={field.name}
|
||||||
|
type={field.field_type}
|
||||||
|
bind:value={responseData[field.name]}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
<button type="submit" class="submit-button" disabled={isSubmitting}>
|
||||||
|
{isSubmitting ? 'Submitting...' : 'Submit Response'}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
<h2>Submissions</h2>
|
||||||
|
{#if submissions.length > 0}
|
||||||
|
<ul class="submissions-list">
|
||||||
|
{#each submissions as submission}
|
||||||
|
<li class="submission-item">
|
||||||
|
{formatSubmissionData(submission.data)}
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{:else}
|
||||||
|
<p>No submissions yet.</p>
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<div class="loading">Loading...</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2 {
|
||||||
|
color: #2c3e50;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-group {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: #2c3e50;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus,
|
||||||
|
select:focus,
|
||||||
|
textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #42b983;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
min-height: 100px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-button {
|
||||||
|
background-color: #42b983;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 0.8rem 1.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-button:hover:not(:disabled) {
|
||||||
|
background-color: #3aa876;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-button:disabled {
|
||||||
|
background-color: #ccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submissions-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submission-item {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-left: 4px solid #42b983;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
text-align: center;
|
||||||
|
color: #666;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
</style>
|
6
frontend/src/routes/+layout.svelte
Normal file
6
frontend/src/routes/+layout.svelte
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<script>
|
||||||
|
import '../app.css';
|
||||||
|
let { children } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{@render children?.()}
|
1
frontend/src/routes/+layout.ts
Normal file
1
frontend/src/routes/+layout.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const ssr = false;
|
@ -3,21 +3,88 @@
|
|||||||
import { getForms } from '../lib/api';
|
import { getForms } from '../lib/api';
|
||||||
import type { Form } from '../lib/types';
|
import type { Form } from '../lib/types';
|
||||||
|
|
||||||
let forms: any;
|
let forms: Form[] = [];
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
forms = await getForms();
|
forms = await getForms();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1>Form Management Tool</h1>
|
<div class="container">
|
||||||
|
<h1>Formies</h1>
|
||||||
|
<a href="/create" class="create-button">Create a New Form</a>
|
||||||
|
|
||||||
<a href="/create">Create a New Form</a>
|
{#if forms.length > 0}
|
||||||
|
<ul class="forms-list">
|
||||||
<ul>
|
|
||||||
{#each forms as form}
|
{#each forms as form}
|
||||||
<li>
|
<li class="form-item">
|
||||||
<a href={`/form/${form.id}`}>{form.name}</a>
|
<a href={`/form/${form.id}`} class="form-link">
|
||||||
|
{form.name}
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
{:else}
|
||||||
|
<div class="empty-state">
|
||||||
|
<p>No forms created yet. Create your first form to get started!</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #2c3e50;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-button {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #42b983;
|
||||||
|
color: white;
|
||||||
|
padding: 0.8rem 1.5rem;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-button:hover {
|
||||||
|
background-color: #3aa876;
|
||||||
|
}
|
||||||
|
|
||||||
|
.forms-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item:hover {
|
||||||
|
transform: translateX(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-link {
|
||||||
|
display: block;
|
||||||
|
padding: 1rem;
|
||||||
|
color: #2c3e50;
|
||||||
|
text-decoration: none;
|
||||||
|
border-left: 4px solid #42b983;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
color: #666;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
7
frontend/src/routes/+page.ts
Normal file
7
frontend/src/routes/+page.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import session from '$lib/session.svelte';
|
||||||
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export async function load() {
|
||||||
|
const page = session.loggedIn() ? '/' : '/login';
|
||||||
|
redirect(307, page);
|
||||||
|
}
|
@ -1,62 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { createForm } from '../../lib/api';
|
|
||||||
import type { FormField } from '../../lib/types';
|
|
||||||
|
|
||||||
let name = '';
|
|
||||||
let fields: FormField[] = [];
|
|
||||||
|
|
||||||
function addField() {
|
|
||||||
// Use a new array assignment to trigger reactivity
|
|
||||||
fields = [...fields, { label: '', name: '', field_type: 'text' }];
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeField(index: number) {
|
|
||||||
// Reassign to trigger reactivity
|
|
||||||
fields = fields.filter((_, i) => i !== index);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveForm() {
|
|
||||||
try {
|
|
||||||
await createForm(name, fields);
|
|
||||||
alert('Form created successfully!');
|
|
||||||
location.href = '/';
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to create form:', error);
|
|
||||||
alert('An error occurred while creating the form.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h1>Create Form</h1>
|
|
||||||
|
|
||||||
<label>
|
|
||||||
Form Name:
|
|
||||||
<input type="text" bind:value={name} placeholder="Enter form name" />
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<h2>Fields</h2>
|
|
||||||
{#each fields as field, i}
|
|
||||||
<div>
|
|
||||||
<label>
|
|
||||||
Label:
|
|
||||||
<input type="text" bind:value={field.label} placeholder="Enter field label" />
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Name:
|
|
||||||
<input type="text" bind:value={field.name} placeholder="Enter field name" />
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Type:
|
|
||||||
<select bind:value={field.field_type}>
|
|
||||||
<option value="text">Text</option>
|
|
||||||
<option value="number">Number</option>
|
|
||||||
<option value="date">Date</option>
|
|
||||||
<option value="textarea">Textarea</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<button on:click={() => removeField(i)}>Remove</button>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
<button on:click={addField}>Add Field</button>
|
|
||||||
<button on:click={saveForm} disabled={!name || fields.length === 0}> Save Form </button>
|
|
@ -1,61 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
import { getForms, getSubmissions, submitForm } from '../../../lib/api';
|
|
||||||
import type { Form, Submission } from '../../../lib/types';
|
|
||||||
import { page } from '$app/stores';
|
|
||||||
|
|
||||||
export let params: { id: string };
|
|
||||||
console.log('params.id:', params);
|
|
||||||
let form: any | null = null;
|
|
||||||
let submissions: any[] = [];
|
|
||||||
let responseData: Record<string, any> = {};
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
const { id } = $page.params; // Use $page.params to access route parameters
|
|
||||||
if (id) {
|
|
||||||
form = await getForms().then((forms) => forms.find((f: any) => f.id === id) || null);
|
|
||||||
submissions = await getSubmissions(id);
|
|
||||||
} else {
|
|
||||||
console.error('Route parameter id is missing');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function submitResponse() {
|
|
||||||
const { id } = $page.params; // Use $page.params to access route parameters
|
|
||||||
await submitForm(id, responseData);
|
|
||||||
alert('Response submitted successfully!');
|
|
||||||
submissions = await getSubmissions(params.id); // Refresh submissions
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h1>{form?.name}</h1>
|
|
||||||
|
|
||||||
{#if form}
|
|
||||||
<form on:submit|preventDefault={submitResponse}>
|
|
||||||
{#each form.fields as field}
|
|
||||||
<div>
|
|
||||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
|
||||||
<label>{field.label}</label>
|
|
||||||
{#if field.field_type === 'text'}
|
|
||||||
<input type="text" bind:value={responseData[field.name]} />
|
|
||||||
{:else if field.field_type === 'number'}
|
|
||||||
<input type="number" bind:value={responseData[field.name]} />
|
|
||||||
{:else if field.field_type === 'date'}
|
|
||||||
<input type="date" bind:value={responseData[field.name]} />
|
|
||||||
{:else if field.field_type === 'textarea'}
|
|
||||||
<textarea bind:value={responseData[field.name]}></textarea>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
<button type="submit">Submit</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<h2>Submissions</h2>
|
|
||||||
<ul>
|
|
||||||
{#each submissions as submission}
|
|
||||||
<li>{JSON.stringify(submission.data)}</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
{:else}
|
|
||||||
<p>Loading...</p>
|
|
||||||
{/if}
|
|
105
frontend/src/routes/login/+page.svelte
Normal file
105
frontend/src/routes/login/+page.svelte
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { adminLogin } from '$lib/api';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
let username = '';
|
||||||
|
let password = '';
|
||||||
|
let errorMessage = '';
|
||||||
|
let loading = false;
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
async function handleSubmit(event: any) {
|
||||||
|
event.preventDefault();
|
||||||
|
errorMessage = '';
|
||||||
|
loading = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const token = await adminLogin({ username, password });
|
||||||
|
dispatch('login', { token }); // Dispatch the token to the parent component or handle it here
|
||||||
|
alert('Login successful!');
|
||||||
|
} catch (error: any) {
|
||||||
|
errorMessage = error.message || 'Login failed. Please try again.';
|
||||||
|
} finally {
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form on:submit={handleSubmit} class="login-form">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input
|
||||||
|
id="username"
|
||||||
|
type="text"
|
||||||
|
class="input-field"
|
||||||
|
bind:value={username}
|
||||||
|
placeholder="Enter your username"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input
|
||||||
|
id="password"
|
||||||
|
type="password"
|
||||||
|
class="input-field"
|
||||||
|
bind:value={password}
|
||||||
|
placeholder="Enter your password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button type="submit" class="submit-btn" disabled={loading}>
|
||||||
|
{loading ? 'Logging in...' : 'Login'}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#if errorMessage}
|
||||||
|
<div class="error-message">{errorMessage}</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if loading}
|
||||||
|
<div class="loading">Loading...</div>
|
||||||
|
{/if}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.login-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-width: 300px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-field {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn:disabled {
|
||||||
|
background-color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: red;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
8
frontend/src/routes/login/+page.ts
Normal file
8
frontend/src/routes/login/+page.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import session from '$lib/session.svelte';
|
||||||
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export async function load() {
|
||||||
|
if (session.loggedIn()) {
|
||||||
|
redirect(307, '/');
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user