diff --git a/.editorconfig b/.editorconfig index d23736ab..c8778a93 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,7 +7,7 @@ end_of_line = lf indent_style = space insert_final_newline = true trim_trailing_whitespace = true -max_line_length = 92 +max_line_length = 100 [*.py] indent_size = 4 diff --git a/frontend/src/lib/components/modals/_components/base_modal.svelte b/frontend/src/lib/components/modals/_components/base_modal.svelte new file mode 100644 index 00000000..308514ff --- /dev/null +++ b/frontend/src/lib/components/modals/_components/base_modal.svelte @@ -0,0 +1,38 @@ + + + +
+ {@render children()} +
+ + + + + +
diff --git a/frontend/src/lib/components/modals/_utils/history.svelte.ts b/frontend/src/lib/components/modals/_utils/history.svelte.ts new file mode 100644 index 00000000..3f0f38e8 --- /dev/null +++ b/frontend/src/lib/components/modals/_utils/history.svelte.ts @@ -0,0 +1,45 @@ +import type { FormConfig } from '../types'; + +export function create_form_history(initial_form: keyof T) { + let history = $state([initial_form]); + + function go_to_form(form: keyof T) { + // if navigating to a form thats already in the history stack, + // truncate the stack upto the most recent occurance of that form + // (avoiding duplicate entiries) + const form_index = history.findIndex((entry) => entry === form); + if (form_index > -1) { + // if exists + history = history.slice(0, form_index + 1); + } else { + // otherwise, push current form to prev_form_history + history.push(form); + } + return history.at(-1); + } + + function go_back() { + if (history.length > 1) { + // remove current form from history stack + history.pop(); + } + return history.at(-1); + } + + function go_next(forms: Record) { + const current_form_index = Object.keys(forms).indexOf(history.at(-1) as string); + const next_index = current_form_index + 1; + if (next_index < Object.keys(forms).length) { + go_to_form(Object.keys(forms)[next_index] as keyof T); + } + } + + return { + get history() { + return history; + }, + go_to_form, + go_back, + go_next + }; +} diff --git a/frontend/src/lib/components/modals/auth/index.svelte b/frontend/src/lib/components/modals/auth/index.svelte deleted file mode 100644 index b286ce43..00000000 --- a/frontend/src/lib/components/modals/auth/index.svelte +++ /dev/null @@ -1,95 +0,0 @@ - - - modalsStore.close('auth')} -> - - - diff --git a/frontend/src/lib/components/modals/auth/types.ts b/frontend/src/lib/components/modals/auth/types.ts deleted file mode 100644 index bbe31eb0..00000000 --- a/frontend/src/lib/components/modals/auth/types.ts +++ /dev/null @@ -1,15 +0,0 @@ -import forms from './forms'; - -export type FormSubmitData = { - [key: string]: string | number | undefined | FormSubmitData | Array; -}; - -export type Forms = keyof typeof forms; - -export type FormsState = { [K in Forms]: object }; - -export type FormProps = { - forms_state: FormsState; - update_forms_state: (form: Forms, data: FormSubmitData) => void; - goto_form: (form: Forms) => void; -}; diff --git a/frontend/src/lib/components/modals/index.svelte b/frontend/src/lib/components/modals/index.svelte index 89843999..f2c784af 100644 --- a/frontend/src/lib/components/modals/index.svelte +++ b/frontend/src/lib/components/modals/index.svelte @@ -1,5 +1,7 @@ - + + diff --git a/frontend/src/lib/components/modals/auth/forms/index.ts b/frontend/src/lib/components/modals/modal_auth/forms/index.ts similarity index 100% rename from frontend/src/lib/components/modals/auth/forms/index.ts rename to frontend/src/lib/components/modals/modal_auth/forms/index.ts diff --git a/frontend/src/lib/components/modals/auth/forms/join.svelte b/frontend/src/lib/components/modals/modal_auth/forms/join.svelte similarity index 97% rename from frontend/src/lib/components/modals/auth/forms/join.svelte rename to frontend/src/lib/components/modals/modal_auth/forms/join.svelte index 3a790dce..7570bb40 100644 --- a/frontend/src/lib/components/modals/auth/forms/join.svelte +++ b/frontend/src/lib/components/modals/modal_auth/forms/join.svelte @@ -4,11 +4,12 @@ import QuibbleLogo from '$lib/components/icons/logos/quibble.svelte'; import QuibbleTextLogo from '$lib/components/icons/logos/quibble_text.svelte'; import { cn } from '$lib/functions/classnames'; - import type { FormProps } from '../types'; + import type { FormProps } from '../../types'; + import forms from '../forms'; import type { SubmitFunction } from '@sveltejs/kit'; import type { HTMLInputAttributes } from 'svelte/elements'; - let { update_forms_state, goto_form }: FormProps = $props(); + let { update_forms_state, goto_form }: FormProps = $props(); let auth_type = $state<'login' | 'register'>('login'); let password_type = $state('password'); diff --git a/frontend/src/lib/components/modals/auth/forms/profile_create.svelte b/frontend/src/lib/components/modals/modal_auth/forms/profile_create.svelte similarity index 94% rename from frontend/src/lib/components/modals/auth/forms/profile_create.svelte rename to frontend/src/lib/components/modals/modal_auth/forms/profile_create.svelte index dd0d31ac..9535d2b7 100644 --- a/frontend/src/lib/components/modals/auth/forms/profile_create.svelte +++ b/frontend/src/lib/components/modals/modal_auth/forms/profile_create.svelte @@ -3,10 +3,11 @@ import QuibbleLogo from '$lib/components/icons/logos/quibble.svelte'; import QuibbleTextLogo from '$lib/components/icons/logos/quibble_text.svelte'; import { cn } from '$lib/functions/classnames'; - import type { FormProps } from '../types'; + import type { FormProps } from '../../types'; + import forms from '../forms'; import type { SubmitFunction } from '@sveltejs/kit'; - let { forms_state, update_forms_state, goto_form }: FormProps = $props(); + let { forms_state, update_forms_state, goto_form }: FormProps = $props(); let errors = $state | undefined>(); let pending = $state(false); diff --git a/frontend/src/lib/components/modals/auth/forms/profile_select.svelte b/frontend/src/lib/components/modals/modal_auth/forms/profile_select.svelte similarity index 89% rename from frontend/src/lib/components/modals/auth/forms/profile_select.svelte rename to frontend/src/lib/components/modals/modal_auth/forms/profile_select.svelte index b9c820e5..13332a00 100644 --- a/frontend/src/lib/components/modals/auth/forms/profile_select.svelte +++ b/frontend/src/lib/components/modals/modal_auth/forms/profile_select.svelte @@ -6,14 +6,16 @@ import QuibbleLogo from '$lib/components/icons/logos/quibble.svelte'; import QuibbleTextLogo from '$lib/components/icons/logos/quibble_text.svelte'; import Avatar from '$lib/components/ui/avatar.svelte'; + import { toast } from '$lib/components/ui/toast/toast.svelte'; import { createModalsStore } from '$lib/stores/modals.svelte'; - import type { FormProps } from '../types'; + import type { FormProps } from '../../types'; + import forms from '../forms'; import type { SubmitFunction } from '@sveltejs/kit'; import { onMount } from 'svelte'; type Profile = components['schemas']['Profile']; - let { update_forms_state, forms_state, goto_form }: FormProps = $props(); + let { update_forms_state, forms_state, goto_form }: FormProps = $props(); let pending = $state(false); let status_text = $state(null); @@ -26,10 +28,11 @@ pending = true; status_text = 'Setting up profile...'; - return async () => { + return async ({ formData }) => { // re-run load functions and close this modal await invalidateAll(); modalsStore.close('auth'); + toast.push({ message: `Logged in as u/${String(formData.get('profile_username'))}` }); pending = false; status_text = null; @@ -88,6 +91,7 @@ {#each profiles as profile}
+ + + {/if} + + diff --git a/frontend/src/lib/components/modals/modal_create_community/forms/index.ts b/frontend/src/lib/components/modals/modal_create_community/forms/index.ts new file mode 100644 index 00000000..1ec110ad --- /dev/null +++ b/frontend/src/lib/components/modals/modal_create_community/forms/index.ts @@ -0,0 +1,6 @@ +const forms = { + introduction: import('./introduction.svelte'), + topics: import('./topics.svelte') +}; + +export default forms; diff --git a/frontend/src/lib/components/modals/modal_create_community/forms/introduction.svelte b/frontend/src/lib/components/modals/modal_create_community/forms/introduction.svelte new file mode 100644 index 00000000..84d2557d --- /dev/null +++ b/frontend/src/lib/components/modals/modal_create_community/forms/introduction.svelte @@ -0,0 +1,96 @@ + + +
+
+

Introduce your community

+

+ Give your community a name and a description that reflects its purpose and vibe. This will + help others discover and connect with it. +

+
+ +
+ + +
+
+
+
+
+
+ +
+ q/{name || 'communityname'} +
+ 1 member + + 1 online +
+
+
+

+ {description || 'Community description'} +

+
+
+
+ +
diff --git a/frontend/src/lib/components/modals/modal_create_community/forms/topics.svelte b/frontend/src/lib/components/modals/modal_create_community/forms/topics.svelte new file mode 100644 index 00000000..ed0eb35f --- /dev/null +++ b/frontend/src/lib/components/modals/modal_create_community/forms/topics.svelte @@ -0,0 +1,20 @@ + + +
+
+

Choose topics

+

+ Select up to 3 topics that represent what your community is about. This will help people with + similar interests discover it. +

+
+
diff --git a/frontend/src/lib/components/modals/modal_create_community/index.svelte b/frontend/src/lib/components/modals/modal_create_community/index.svelte new file mode 100644 index 00000000..f7f9e0f0 --- /dev/null +++ b/frontend/src/lib/components/modals/modal_create_community/index.svelte @@ -0,0 +1,103 @@ + + + + {#await form then Form} + + {/await} + +
+
+ {#each Object.keys(forms) as _form} + {@const is_active = _form === form_history.history.at(-1)} + + {/each} +
+
+ + +
+
+
diff --git a/frontend/src/lib/components/modals/modal_create_post/.gitkeep b/frontend/src/lib/components/modals/modal_create_post/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/lib/components/modals/types.ts b/frontend/src/lib/components/modals/types.ts new file mode 100644 index 00000000..d8e4c600 --- /dev/null +++ b/frontend/src/lib/components/modals/types.ts @@ -0,0 +1,15 @@ +export type FormConfig = Record>; + +export type Forms = keyof Config; + +export type FormsState = { [K in keyof Config]: object }; + +export type FormProps = { + forms_state: FormsState; + update_forms_state: (form: keyof Config, data: FormSubmitData) => void; + goto_form: (form: keyof Config) => void; +}; + +export type FormSubmitData = { + [key: string]: unknown | FormSubmitData | Array; +}; diff --git a/frontend/src/lib/components/sidebar.svelte b/frontend/src/lib/components/sidebar.svelte index 81520081..39192fa9 100644 --- a/frontend/src/lib/components/sidebar.svelte +++ b/frontend/src/lib/components/sidebar.svelte @@ -1,9 +1,23 @@
-
diff --git a/frontend/src/lib/components/ui/toast/toast.svelte.ts b/frontend/src/lib/components/ui/toast/toast.svelte.ts new file mode 100644 index 00000000..2e89773f --- /dev/null +++ b/frontend/src/lib/components/ui/toast/toast.svelte.ts @@ -0,0 +1,42 @@ +import { generate_id } from '$lib/functions/generate_id'; + +type ToastIn = { + message: string; + class?: string; + duration?: number; +}; + +type Toast = ToastIn & { + id: string; + timer: NodeJS.Timeout; +}; + +let toasts = $state([]); + +export const toast = { + get toasts() { + return toasts; + }, + push: (toast: ToastIn) => { + const exists = toasts.find((t) => t.message === toast.message); + if (exists !== undefined) return exists.id; + + const new_toast: Toast = { + ...toast, + id: generate_id(), + timer: setTimeout(() => { + toasts = toasts.filter((t) => t.id !== new_toast.id); + }, toast.duration ?? 3000) + }; + + toasts.push(new_toast); + return new_toast.id; + }, + dismiss: (id: string) => { + const toast = toasts.find((t) => t.id === id); + if (toast === undefined) return; + + clearTimeout(toast.timer); + toasts = toasts.filter((t) => t.id !== id); + } +}; diff --git a/frontend/src/lib/components/ui/toast/toaster.svelte b/frontend/src/lib/components/ui/toast/toaster.svelte new file mode 100644 index 00000000..b09444d9 --- /dev/null +++ b/frontend/src/lib/components/ui/toast/toaster.svelte @@ -0,0 +1,19 @@ + + +
+ {#each toast.toasts as t (t.id)} +
+ + {t.message} + +
+ {/each} +
diff --git a/frontend/src/lib/functions/generate_id.ts b/frontend/src/lib/functions/generate_id.ts new file mode 100644 index 00000000..4307284e --- /dev/null +++ b/frontend/src/lib/functions/generate_id.ts @@ -0,0 +1,8 @@ +/** + * @summary Generate random string id with given segments and length + * @params + * segments: string = 3 + * length: string = 4 + */ +export const generate_id = (segments = 3, length = 4) => + Array.from({ length: segments }, () => Math.random().toString(16).slice(-length)).join(''); diff --git a/frontend/src/lib/stores/modals.svelte.ts b/frontend/src/lib/stores/modals.svelte.ts index ccf3a881..225ad0f6 100644 --- a/frontend/src/lib/stores/modals.svelte.ts +++ b/frontend/src/lib/stores/modals.svelte.ts @@ -1,6 +1,6 @@ import { SvelteMap } from 'svelte/reactivity'; -const modals_map = ['auth'] as const; +const modals_map = ['auth', 'create_community'] as const; type Modals = (typeof modals_map)[number]; const modals = $state(new SvelteMap(modals_map.map((item) => [item, false]))); diff --git a/frontend/src/routes/(app)/+page.svelte b/frontend/src/routes/(app)/+page.svelte index 0b1be8ff..90f214be 100644 --- a/frontend/src/routes/(app)/+page.svelte +++ b/frontend/src/routes/(app)/+page.svelte @@ -2,6 +2,7 @@ import Post from '$lib/components/post.svelte'; import PostsHeader from '$lib/components/posts_header.svelte'; import Avatar from '$lib/components/ui/avatar.svelte'; + import { toast } from '$lib/components/ui/toast/toast.svelte'; import Quibble_404_2 from '$lib/components/vectors/quibble_404_2.svelte'; import { createAuthStore } from '$lib/stores/auth.svelte'; import { createModalsStore } from '$lib/stores/modals.svelte'; @@ -20,6 +21,7 @@ // open post create modal } else { modalsStore.open('auth'); + toast.push({ message: 'Please login to do this action!' }); } } @@ -46,13 +48,8 @@ aria-label="404 action" onclick={handle_404_action_btn_click} > - {#if authStore.state.is_authenticated} - - Create - {:else} - - Join in - {/if} + + Create
diff --git a/frontend/src/routes/(app)/settings/profile/+page.server.ts b/frontend/src/routes/(app)/settings/profile/+page.server.ts index af9b358c..3c94f98d 100644 --- a/frontend/src/routes/(app)/settings/profile/+page.server.ts +++ b/frontend/src/routes/(app)/settings/profile/+page.server.ts @@ -40,7 +40,7 @@ export const actions = { }, select: async ({ request, cookies }) => { const form_data = await request.formData(); - const profile_id = form_data.get('profile_id') as string; + const profile_id = String(form_data.get('profile_id')); cookies.set('auth_user_profile_id', profile_id, { httpOnly: true, diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index 74668d8d..f84b881f 100644 --- a/frontend/src/routes/+layout.svelte +++ b/frontend/src/routes/+layout.svelte @@ -3,6 +3,7 @@ import Header from '$lib/components/header.svelte'; import Modals from '$lib/components/modals/index.svelte'; import Sidebar from '$lib/components/sidebar.svelte'; + import Toaster from '$lib/components/ui/toast/toaster.svelte'; import { cn } from '$lib/functions/classnames'; import { createAuthStore } from '$lib/stores/auth.svelte'; import '../styles/app.css'; @@ -36,6 +37,9 @@ }; + + +
diff --git a/frontend/src/styles/smiz.css b/frontend/src/styles/smiz.css index e7bf3aca..bdbc8683 100644 --- a/frontend/src/styles/smiz.css +++ b/frontend/src/styles/smiz.css @@ -78,11 +78,13 @@ } [data-smiz-modal-overlay='hidden'] { - background-color: oklch(var(--b3) / 0); + /* background-color: oklch(var(--b3) / 0); */ + background-color: #0000; } [data-smiz-modal-overlay='visible'] { - background-color: oklch(var(--b3) / 0.55); + /* background-color: oklch(var(--b3) / 0.55); */ + background-color: #0006; } [data-smiz-modal-content] {