Added Basic xstate store management POC

This commit is contained in:
Samuel Andert 2023-08-01 11:58:34 +02:00
parent 3d2960390a
commit 125d7d997e
10 changed files with 224 additions and 113 deletions

View File

@ -1,4 +1,4 @@
<script> <script lang="ts">
import { fetchBalance } from '@wagmi/core'; import { fetchBalance } from '@wagmi/core';
import { onMount } from 'svelte'; import { onMount } from 'svelte';

View File

@ -2,6 +2,7 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { connectWallet } from '$lib/services/wallet/wallet'; import { connectWallet } from '$lib/services/wallet/wallet';
import WalletConnect from '$lib/WalletConnect.svelte'; import WalletConnect from '$lib/WalletConnect.svelte';
import Send from '$lib/Send.svelte';
// export let id; // export let id;
export let store; export let store;
@ -21,6 +22,7 @@
<div class="mb-4 text-lg font-medium"> <div class="mb-4 text-lg font-medium">
PKP Wallet: <span class="text-blue-600">{$store.pkpWallet.address}</span> PKP Wallet: <span class="text-blue-600">{$store.pkpWallet.address}</span>
</div> </div>
<Send pkpWallet={$store.pkpWallet} />
{/if} {/if}
<WalletConnect /> <WalletConnect />
</div> </div>

View File

@ -0,0 +1,14 @@
<!-- src/lib/components/CheckValidation.svelte -->
<script lang="ts">
export let store;
$: console.log('checkvalidation: ' + $store.isValidated);
</script>
<div class="flex items-center justify-center w-12 h-12 m-10 rounded-full">
{#if $store.isValidated}
<div class="w-full h-full bg-green-500 rounded-full" />
{:else}
<div class="w-full h-full bg-red-500 rounded-full" />
{/if}
</div>

View File

@ -1,10 +1,16 @@
<script lang="ts"> <script lang="ts">
import { superForm, message } from 'sveltekit-superforms/client'; import { superForm } from 'sveltekit-superforms/client';
import { UserSchema } from '$lib/types/UserSchema'; import { UserSchema } from '$lib/types/UserSchema';
import { afterUpdate } from 'svelte'; import { afterUpdate } from 'svelte';
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import { RangeSlider, SlideToggle } from '@skeletonlabs/skeleton'; import { RangeSlider, SlideToggle } from '@skeletonlabs/skeleton';
export let store;
export let services;
let isStoreLoaded = false;
$: if ($store) isStoreLoaded = true;
const initialFormData = { const initialFormData = {
name: '', name: '',
email: '', email: '',
@ -17,19 +23,42 @@
const fields = ['name', 'email', 'about', 'age', 'favoriteFood', 'slider', 'toggle']; const fields = ['name', 'email', 'about', 'age', 'favoriteFood', 'slider', 'toggle'];
const { form, errors, validate, constraints, capture, restore } = superForm(initialFormData, { const { form, errors, validate, constraints } = superForm(initialFormData, {
validators: UserSchema, validators: UserSchema,
warnings: { warnings: {
noValidationAndConstraints: false noValidationAndConstraints: false
}, },
validationMethod: 'oninput', // Trigger validation on input events validationMethod: 'oninput', // Trigger validation on input events
// Set the clearOnSubmit option to clear both errors and message on submit
clearOnSubmit: 'errors-and-message' clearOnSubmit: 'errors-and-message'
}); });
export const snapshot = { capture, restore };
const successMessage = writable<string | null>(null); const successMessage = writable<string | null>(null);
// Update the isValidated property of the store whenever the errors object changes
$: {
$store.isValidated = !(
$errors.name ||
$errors.email ||
$errors.about ||
$errors.age ||
$errors.favoriteFood
);
}
$: {
if (
!$errors.name &&
!$errors.email &&
!$errors.about &&
!$errors.age &&
!$errors.favoriteFood
) {
services.validationRecipe.validateMe().send('VALIDATE');
} else {
services.validationRecipe.validateMe().send('INVALIDATE');
}
}
async function handleSubmit() { async function handleSubmit() {
// Manually validate the form // Manually validate the form
const validationResult = await validate(); const validationResult = await validate();
@ -61,7 +90,10 @@
}); });
</script> </script>
<div class="flex items-center justify-center min-h-screen overflow-scroll"> {#if isStoreLoaded}
<div class="flex items-center justify-center min-h-screen overflow-scroll">
<div class="w-full">
STORE: {JSON.stringify($store)}
{#if $successMessage} {#if $successMessage}
<!-- Display the success message instead of the form --> <!-- Display the success message instead of the form -->
<aside class="w-full max-w-md p-4 alert variant-ghost" id="message-container"> <aside class="w-full max-w-md p-4 alert variant-ghost" id="message-container">
@ -90,8 +122,8 @@
<span class="block mb-2 font-semibold text-red-500">{$errors[field]}</span> <span class="block mb-2 font-semibold text-red-500">{$errors[field]}</span>
{:else} {:else}
<label for={field} class="block mb-2 font-semibold text-white" <label for={field} class="block mb-2 font-semibold text-white"
>{field.charAt(0).toUpperCase() + field.slice(1)}</label >{field.charAt(0).toUpperCase() + field.slice(1)}
> </label>
{/if} {/if}
{#if field === 'about'} {#if field === 'about'}
@ -127,7 +159,14 @@
{...constraints[field]} {...constraints[field]}
/> />
{:else if field === 'slider'} {:else if field === 'slider'}
<RangeSlider name={field} bind:value={$form[field]} min={0} max={100} step={1} ticked> <RangeSlider
name={field}
bind:value={$form[field]}
min={0}
max={100}
step={1}
ticked
>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="text-xs">{$form[field]} / 100</div> <div class="text-xs">{$form[field]} / 100</div>
</div> </div>
@ -146,18 +185,15 @@
{/if} {/if}
</div> </div>
{/each} {/each}
<button <button
type="submit" type="submit"
class="w-full px-4 py-2 mt-4 text-white bg-blue-500 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500" class="w-full px-4 py-2 mt-4 text-white bg-blue-500 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
disabled={$errors.name || disabled={!$store.isValidated}
$errors.email ||
$errors.about ||
$errors.age ||
$errors.favoriteFood}
> >
Submit Submit
</button> </button>
</form> </form>
{/if} {/if}
</div> </div>
</div>
{/if}

View File

@ -1,8 +0,0 @@
/src/lib/components/examples/Form.svelte:98:7 'type' attribute cannot be dynamic if input uses two-way binding
/src/lib/components/examples/Form.svelte:98:7
96 | <input
97 | name={field}
98 | type={field === 'age' ? 'number' : 'text'}
^
99 | class="w-full px-3 py-2 bg-transparent border-gray-100 rounded-md border-1 ring-0 ring-white focus:outline-none focus:ring-2 focus:ring-blue-500"
100 | bind:value={$form[field]}

View File

@ -0,0 +1 @@
{"core":{},"composite.Interpreter2":{"id":"validation"},"helloEarthAlert":{}}

View File

@ -0,0 +1,27 @@
import { createMachine, interpret } from 'xstate';
const validationMachine = createMachine({
id: 'validation',
initial: 'notValidated',
states: {
notValidated: {
on: {
VALIDATE: 'validated'
}
},
validated: {
on: {
INVALIDATE: 'notValidated'
}
}
}
});
export function validateMe() {
const service = interpret(validationMachine).onTransition((state) => {
console.log('Current validation state:', state.value);
}).start();
return service;
}

View File

@ -29,7 +29,7 @@ const validationMessages = {
}; };
export const UserSchema = z.object({ export const UserSchema = z.object({
name: z.string().min(3, validationMessages.name.minLength).max(10, validationMessages.name.maxLength), name: z.string().nonempty('Name is required.').min(3, validationMessages.name.minLength).max(10, validationMessages.name.maxLength),
email: z.string().email(validationMessages.email.isEmail), email: z.string().email(validationMessages.email.isEmail),
about: z.string().max(500, validationMessages.about.maxLength), about: z.string().max(500, validationMessages.about.maxLength),
age: z.number().min(18, validationMessages.age.min).max(120, validationMessages.age.max), age: z.number().min(18, validationMessages.age.min).max(120, validationMessages.age.max),

View File

@ -0,0 +1,39 @@
<!-- src/routes/form/+page.svelte -->
<script lang="ts">
import Composite from '$lib/core/Composite.svelte';
let composite = {
id: 'testform',
store: {
isValidated: false
},
layout: {
areas: `
"checkvalidation form"
`,
columns: '1fr 3fr',
rows: 'auto'
},
children: [
{
id: 'checkvalidation',
component: 'CheckValidation',
slot: 'checkvalidation',
map: {
isValidated: '@testform:isValidated'
}
},
{
id: 'form',
component: 'Form',
slot: 'form',
map: {
isValidated: '@testform:isValidated'
},
services: ['validationRecipe', 'helloEarthAlert']
}
]
};
</script>
<Composite {composite} />

View File

@ -6,7 +6,7 @@
store: { store: {
title: 'Hello Earth', title: 'Hello Earth',
description: description:
'Here you can find all the references, how to use the store and mapping logic of store to store and data to store maps', 'how to use the store and mapping logic of store to store and data to store maps',
helloMapMe: 'this is going to be mapped' helloMapMe: 'this is going to be mapped'
}, },
layout: { layout: {