From 95ea2a5f2373920655c91a89a76b03cacf72fc42 Mon Sep 17 00:00:00 2001 From: john Date: Wed, 11 Jun 2025 23:12:03 +0200 Subject: [PATCH 01/39] fix loading feed --- src/app/api/client.ts | 8 +-- src/app/feed/pages/HomePage.tsx | 88 ++++++++++++++---------------- src/app/feed/posts/postsService.ts | 26 +++------ src/utils/delay.ts | 3 + 4 files changed, 54 insertions(+), 71 deletions(-) create mode 100644 src/utils/delay.ts diff --git a/src/app/api/client.ts b/src/app/api/client.ts index 19b8899..63cce0c 100644 --- a/src/app/api/client.ts +++ b/src/app/api/client.ts @@ -1,10 +1,8 @@ import { paths } from './schema.ts' -import createClient, { Client, Middleware } from 'openapi-fetch' +import createClient, { Middleware } from 'openapi-fetch' import { dispatchMessage } from '../messageBus/messageBus.ts' -export type ApiClient = Client - -export function initClient(): ApiClient { +export const initClient = () => { const client = createClient({ baseUrl: import.meta.env.VITE_API_URL }) const UnauthorizedHandlerMiddleware: Middleware = { async onResponse({ response }) { @@ -17,3 +15,5 @@ export function initClient(): ApiClient { client.use(UnauthorizedHandlerMiddleware) return client } + +export type ApiClient = ReturnType diff --git a/src/app/feed/pages/HomePage.tsx b/src/app/feed/pages/HomePage.tsx index 738d168..c470d39 100644 --- a/src/app/feed/pages/HomePage.tsx +++ b/src/app/feed/pages/HomePage.tsx @@ -1,4 +1,4 @@ -import { useCallback, useRef, useState } from 'react' +import { useRef, useState } from 'react' import { PostsService } from '../posts/postsService.ts' import { useUser } from '../../user/user.ts' import { MediaService } from '../../media/mediaService.ts' @@ -11,6 +11,7 @@ import { Post } from '../posts/posts.ts' import { produce, WritableDraft } from 'immer' import PostItem from '../components/PostItem.tsx' import { useIntersectionLoad } from '../../../hooks/useIntersectionLoad.ts' +import { delay } from '../../../utils/delay.ts' interface HomePageProps { postsService: PostsService @@ -24,13 +25,6 @@ export default function HomePage({ postsService, mediaService }: HomePageProps) useSaveSignupCodeToLocalStorage() const [isSubmitting, setIsSubmitting] = useState(false) - const fetchPosts = useCallback( - async (cursor: string | null, amount: number | null) => { - return postsService.loadPublicFeed(cursor, amount) - }, - [postsService], - ) - const [posts, setPosts] = useState([]) const [hasMore, setHasMore] = useState(true) const [error, setError] = useState(null) @@ -38,56 +32,54 @@ export default function HomePage({ postsService, mediaService }: HomePageProps) const cursor = useRef(null) const loading = useRef(false) - const loadNextPage = useCallback(async () => { + const loadNextPage = async () => { if (loading.current || !hasMore || error) return loading.current = true try { - const delay = new Promise((resolve) => setTimeout(resolve, 500)) - const pagePromise = fetchPosts(cursor.current, PageSize) - const [page] = await Promise.all([pagePromise, delay]) - setHasMore(page.length >= PageSize) - cursor.current = page.at(-1)?.postId ?? null - setPosts((prev) => [...prev, ...page]) + const [{ posts, next }] = await Promise.all([ + postsService.loadPublicFeed(cursor.current, PageSize), + delay(500), + ]) + + setHasMore(posts.length >= PageSize) + cursor.current = next + setPosts((prev) => [...prev, ...posts]) } catch (e: unknown) { - const err = e as Error - setError(err.message) + setError((e as Error).message) } finally { loading.current = false } - }, [fetchPosts, hasMore, error]) + } - const onCreatePost = useCallback( - async ( - content: string, - files: { file: File; width: number; height: number }[], - isPublic: boolean, - ) => { - setIsSubmitting(true) - if (user == null) throw new Error('Not logged in') - try { - const media = await Promise.all( - files.map(async ({ file, width, height }) => { - const { mediaId, url } = await mediaService.uploadImage(file) + const onCreatePost = async ( + content: string, + files: { file: File; width: number; height: number }[], + isPublic: boolean, + ) => { + setIsSubmitting(true) + if (user == null) throw new Error('Not logged in') + try { + const media = await Promise.all( + files.map(async ({ file, width, height }) => { + const { mediaId, url } = await mediaService.uploadImage(file) - return { - mediaId, - url, - width, - height, - } - }), - ) - const post = await postsService.createNew(user.id, content, media, isPublic) - setPosts((pages) => [post, ...pages]) - } catch (error) { - console.error('Failed to create post:', error) - } finally { - setIsSubmitting(false) - } - }, - [mediaService, postsService, setPosts, user], - ) + return { + mediaId, + url, + width, + height, + } + }), + ) + const post = await postsService.createNew(user.id, content, media, isPublic) + setPosts((pages) => [post, ...pages]) + } catch (error) { + console.error('Failed to create post:', error) + } finally { + setIsSubmitting(false) + } + } const isLoggedIn = user != null diff --git a/src/app/feed/posts/postsService.ts b/src/app/feed/posts/postsService.ts index e6c2c88..06fa2ff 100644 --- a/src/app/feed/posts/postsService.ts +++ b/src/app/feed/posts/postsService.ts @@ -29,34 +29,22 @@ export class PostsService { return Post.fromDto(response.data.post) } - async loadPublicFeed(cursor: string | null, amount: number | null): Promise { - const response = await this.client.GET('/posts', { - query: { cursor, amount }, - credentials: 'include', - }) - - if (!response.data) { - return [] - } - - return response.data?.posts.map((post) => Post.fromDto(post)) - } - - async loadByAuthor( - username: string, + async loadPublicFeed( cursor: string | null, amount: number | null, - ): Promise { + ): Promise<{ posts: Post[]; next: string | null }> { const response = await this.client.GET('/posts', { - query: { From: cursor ?? undefined, Amount: amount ?? undefined, Author: username }, + params: { + query: { From: cursor ?? undefined, Amount: amount ?? undefined }, + }, credentials: 'include', }) if (!response.data) { - return [] + return { posts: [], next: null } } - return response.data?.posts.map((post) => Post.fromDto(post)) + return { posts: response.data.posts.map(Post.fromDto), next: response.data.next } } async addReaction(postId: string, emoji: string): Promise { diff --git a/src/utils/delay.ts b/src/utils/delay.ts new file mode 100644 index 0000000..9c70749 --- /dev/null +++ b/src/utils/delay.ts @@ -0,0 +1,3 @@ +export function delay(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)) +} From ac36a1e3c203196916b8e5646485bbe7072df0d3 Mon Sep 17 00:00:00 2001 From: john Date: Wed, 11 Jun 2025 23:12:50 +0200 Subject: [PATCH 02/39] v1.20.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c918a60..234a6be 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "femto-webapp", "private": true, - "version": "1.19.0", + "version": "1.20.0", "type": "module", "scripts": { "dev": "vite --host 0.0.0.0", From 52d22ed0d6fdc8cbbea873f5bc8374473dca0835 Mon Sep 17 00:00:00 2001 From: john Date: Wed, 11 Jun 2025 23:18:14 +0200 Subject: [PATCH 03/39] comment out the remember me checkbox --- src/app/auth/pages/LoginPage.tsx | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/app/auth/pages/LoginPage.tsx b/src/app/auth/pages/LoginPage.tsx index 16d1a3f..0aac2e9 100644 --- a/src/app/auth/pages/LoginPage.tsx +++ b/src/app/auth/pages/LoginPage.tsx @@ -96,18 +96,18 @@ export default function LoginPage({ authService }: LoginPageProps) { /> -
- setRememberMe(e.target.checked)} - className="h-4 w-4" - /> - -
+ {/*
*/} + {/* setRememberMe(e.target.checked)}*/} + {/* className="h-4 w-4"*/} + {/* />*/} + {/* */} + {/*
*/} - register instead? + {t('auth.login.register_instead')} {error} diff --git a/src/app/auth/pages/SignupPage.tsx b/src/app/auth/pages/SignupPage.tsx index ab6b376..ec3adba 100644 --- a/src/app/auth/pages/SignupPage.tsx +++ b/src/app/auth/pages/SignupPage.tsx @@ -3,12 +3,12 @@ import { useEffect, useRef, useState, FormEvent, useCallback, Ref } from 'react' import SingleColumnLayout from '../../../layouts/SingleColumnLayout.tsx' import TextInput from '../../../components/inputs/TextInput.tsx' import Button from '../../../components/buttons/Button.tsx' -import AnchorButton from '../../../components/buttons/AnchorButton.tsx' import { invalid, valid, Validation } from '../../../utils/validation.ts' import { AuthService } from '../authService.ts' import LinkButton from '../../../components/buttons/LinkButton.tsx' import NavBar from '../../../components/NavBar.tsx' import NavButton from '../../../components/buttons/NavButton.tsx' +import { useTranslations } from '../../i18n/useTranslations.ts' const SignupCodeKey = 'signupCode' @@ -17,6 +17,7 @@ interface SignupPageProps { } export default function SignupPage({ authService }: SignupPageProps) { + const { t } = useTranslations() const { code } = useParams() const [signupCode, setSignupCode] = useState(null) const [isSubmitting, setIsSubmitting] = useState(false) @@ -31,8 +32,6 @@ export default function SignupPage({ authService }: SignupPageProps) { const userNameInputRef = useRef(null) const passwordInputRef = useRef(null) - const dialogRef = useRef(null) - const navigate = useNavigate() useEffect(() => { @@ -47,10 +46,6 @@ export default function SignupPage({ authService }: SignupPageProps) { theSignupCode = localStorage.getItem(SignupCodeKey) setSignupCode(theSignupCode) } - - if (!theSignupCode) { - dialogRef.current?.showModal() - } }, [code, signupCode]) useEffect(() => {}, [signupCode]) @@ -94,7 +89,7 @@ export default function SignupPage({ authService }: SignupPageProps) { - home + {t('nav.home')} } > @@ -103,6 +98,7 @@ export default function SignupPage({ authService }: SignupPageProps) { -
+
- login instead? + {t('auth.register.login_instead')} {error}
- - -
-

STOP !!!

-

You need an invitation to sign up

-

- I'm surprised you even found your way here without one and honestly I'd prefer it if you - would leave -

-

- If you do want to create an account, you should know who - to contact -

- - I'm sorry I'll go somewhere else :( - -
-
) } interface FormInputProps { id: string + label: string value: string onInput: (value: string) => void error: string | null @@ -179,11 +155,11 @@ interface FormInputProps { ref: Ref } -function FormInput({ id, value, onInput, error, type = 'text', ref }: FormInputProps) { +function FormInput({ id, label, value, onInput, error, type = 'text', ref }: FormInputProps) { return (
{error}
diff --git a/src/app/i18n/en.json b/src/app/i18n/en.json new file mode 100644 index 0000000..6cc6cb5 --- /dev/null +++ b/src/app/i18n/en.json @@ -0,0 +1,19 @@ +{ + "nav.home": "home", + "nav.login": "login", + "nav.register": "register", + "nav.admin": "admin", + "auth.login.cta": "login", + "auth.login.register_instead": "register instead?", + "auth.register.cta": "signup", + "auth.register.login_instead": "login instead?", + "auth.username.label": "username", + "auth.password.label": "password", + "auth.remember_me.label": "stay logged in", + "misc.loading": "wait...", + "nav.logout": "logout", + "post.add_media.cta": "+ add media", + "post.public.label": "public", + "post.submit.cta": "post", + "post.editor.placeholder": "write something..." +} \ No newline at end of file diff --git a/src/app/i18n/translationKeys.ts b/src/app/i18n/translationKeys.ts new file mode 100644 index 0000000..de1c52a --- /dev/null +++ b/src/app/i18n/translationKeys.ts @@ -0,0 +1,24 @@ +export interface Translations { + 'auth.login.cta': string + 'auth.login.register_instead': string + 'auth.password.label': string + 'auth.register.cta': string + 'auth.register.login_instead': string + 'auth.remember_me.label': string + 'auth.username.label': string + + 'misc.loading': string + + 'nav.admin': string + 'nav.home': string + 'nav.login': string + 'nav.logout': string + 'nav.register': string + + 'post.add_media.cta': string + 'post.editor.placeholder': string + 'post.public.label': string + 'post.submit.cta': string +} + +export type TranslationKey = keyof Translations diff --git a/src/app/i18n/useTranslations.ts b/src/app/i18n/useTranslations.ts new file mode 100644 index 0000000..c403be1 --- /dev/null +++ b/src/app/i18n/useTranslations.ts @@ -0,0 +1,13 @@ +import { TranslationKey, Translations } from './translationKeys.ts' +import en from './en.json' assert { type: 'json' } + +export function useTranslations() { + // TODO somehow handle other languages (reactively) + const texts = en as Translations + + function getText(key: K): Translations[K] { + return texts[key] ?? key + } + + return { t: getText } +} diff --git a/src/components/NewPostWidget.tsx b/src/components/NewPostWidget.tsx index ea3496a..d7b4828 100644 --- a/src/components/NewPostWidget.tsx +++ b/src/components/NewPostWidget.tsx @@ -3,6 +3,7 @@ import FancyTextEditor, { TextInputKeyDownEvent } from './inputs/FancyTextEditor import Button from './buttons/Button.tsx' import { openFileDialog } from '../utils/openFileDialog.ts' import makePica from 'pica' +import { useTranslations } from '../app/i18n/useTranslations.ts' interface NewPostWidgetProps { onSubmit: ( @@ -22,6 +23,7 @@ interface Attachment { } export default function NewPostWidget({ onSubmit, isSubmitting = false }: NewPostWidgetProps) { + const { t } = useTranslations() const [content, setContent] = useState('') const [attachments, setAttachments] = useState([]) const [isPublic, setIsPublic] = useState(false) @@ -72,7 +74,7 @@ export default function NewPostWidget({ onSubmit, isSubmitting = false }: NewPos onInput={onContentInput} onKeyDown={onInputKeyDown} className="mb-3" - placeholder="write something..." + placeholder={t('post.editor.placeholder')} /> {attachments.length > 0 && ( @@ -93,7 +95,7 @@ export default function NewPostWidget({ onSubmit, isSubmitting = false }: NewPos
From bc5c2075f4464559d041cc2832689fc332323842 Mon Sep 17 00:00:00 2001 From: john Date: Mon, 16 Jun 2025 21:28:24 +0200 Subject: [PATCH 09/39] some changes --- src/app/auth/components/AuthNavButtons.tsx | 2 +- src/app/auth/pages/LoginPage.tsx | 2 +- src/app/auth/pages/SignupPage.tsx | 2 +- src/app/i18n/translationKeys.ts | 24 ------------- src/app/i18n/translations.ts | 41 ++++++++++++++++++++++ src/app/i18n/{ => translations}/en.json | 0 src/app/i18n/useTranslations.ts | 13 ------- src/components/NewPostWidget.tsx | 2 +- 8 files changed, 45 insertions(+), 41 deletions(-) delete mode 100644 src/app/i18n/translationKeys.ts create mode 100644 src/app/i18n/translations.ts rename src/app/i18n/{ => translations}/en.json (100%) delete mode 100644 src/app/i18n/useTranslations.ts diff --git a/src/app/auth/components/AuthNavButtons.tsx b/src/app/auth/components/AuthNavButtons.tsx index f3161f4..c646888 100644 --- a/src/app/auth/components/AuthNavButtons.tsx +++ b/src/app/auth/components/AuthNavButtons.tsx @@ -1,7 +1,7 @@ import { useUser } from '../../user/user.ts' import NavButton from '../../../components/buttons/NavButton.tsx' import { useLocation } from 'react-router-dom' -import { useTranslations } from '../../i18n/useTranslations.ts' +import { useTranslations } from '../../i18n/translations.ts' export default function AuthNavButtons() { const { t } = useTranslations() diff --git a/src/app/auth/pages/LoginPage.tsx b/src/app/auth/pages/LoginPage.tsx index 215d8da..3838a88 100644 --- a/src/app/auth/pages/LoginPage.tsx +++ b/src/app/auth/pages/LoginPage.tsx @@ -8,7 +8,7 @@ import { useUser } from '../../user/user.ts' import NavBar from '../../../components/NavBar.tsx' import NavButton from '../../../components/buttons/NavButton.tsx' import LinkButton from '../../../components/buttons/LinkButton.tsx' -import { useTranslations } from '../../i18n/useTranslations.ts' +import { useTranslations } from '../../i18n/translations.ts' interface LoginPageProps { authService: AuthService diff --git a/src/app/auth/pages/SignupPage.tsx b/src/app/auth/pages/SignupPage.tsx index ec3adba..2e3a200 100644 --- a/src/app/auth/pages/SignupPage.tsx +++ b/src/app/auth/pages/SignupPage.tsx @@ -8,7 +8,7 @@ import { AuthService } from '../authService.ts' import LinkButton from '../../../components/buttons/LinkButton.tsx' import NavBar from '../../../components/NavBar.tsx' import NavButton from '../../../components/buttons/NavButton.tsx' -import { useTranslations } from '../../i18n/useTranslations.ts' +import { useTranslations } from '../../i18n/translations.ts' const SignupCodeKey = 'signupCode' diff --git a/src/app/i18n/translationKeys.ts b/src/app/i18n/translationKeys.ts deleted file mode 100644 index de1c52a..0000000 --- a/src/app/i18n/translationKeys.ts +++ /dev/null @@ -1,24 +0,0 @@ -export interface Translations { - 'auth.login.cta': string - 'auth.login.register_instead': string - 'auth.password.label': string - 'auth.register.cta': string - 'auth.register.login_instead': string - 'auth.remember_me.label': string - 'auth.username.label': string - - 'misc.loading': string - - 'nav.admin': string - 'nav.home': string - 'nav.login': string - 'nav.logout': string - 'nav.register': string - - 'post.add_media.cta': string - 'post.editor.placeholder': string - 'post.public.label': string - 'post.submit.cta': string -} - -export type TranslationKey = keyof Translations diff --git a/src/app/i18n/translations.ts b/src/app/i18n/translations.ts new file mode 100644 index 0000000..519fc55 --- /dev/null +++ b/src/app/i18n/translations.ts @@ -0,0 +1,41 @@ +import en from './translations/en.json' assert { type: 'json' } + +interface Translation { + 'auth.login.cta': string + 'auth.login.register_instead': string + 'auth.password.label': string + 'auth.register.cta': string + 'auth.register.login_instead': string + 'auth.remember_me.label': string + 'auth.username.label': string + + 'misc.loading': string + + 'nav.admin': string + 'nav.home': string + 'nav.login': string + 'nav.logout': string + 'nav.register': string + + 'post.add_media.cta': string + 'post.editor.placeholder': string + 'post.public.label': string + 'post.submit.cta': string +} + +export type TranslationKey = keyof Translation + +export interface UseTranslations { + t: (key: K) => Translation[K] +} + +export function useTranslations(): UseTranslations { + // TODO somehow handle other languages (reactively) + const texts = en as Translation + + function getText(key: K): Translation[K] { + return texts[key] ?? key + } + + return { t: getText } +} diff --git a/src/app/i18n/en.json b/src/app/i18n/translations/en.json similarity index 100% rename from src/app/i18n/en.json rename to src/app/i18n/translations/en.json diff --git a/src/app/i18n/useTranslations.ts b/src/app/i18n/useTranslations.ts deleted file mode 100644 index c403be1..0000000 --- a/src/app/i18n/useTranslations.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { TranslationKey, Translations } from './translationKeys.ts' -import en from './en.json' assert { type: 'json' } - -export function useTranslations() { - // TODO somehow handle other languages (reactively) - const texts = en as Translations - - function getText(key: K): Translations[K] { - return texts[key] ?? key - } - - return { t: getText } -} diff --git a/src/components/NewPostWidget.tsx b/src/components/NewPostWidget.tsx index d7b4828..c943577 100644 --- a/src/components/NewPostWidget.tsx +++ b/src/components/NewPostWidget.tsx @@ -3,7 +3,7 @@ import FancyTextEditor, { TextInputKeyDownEvent } from './inputs/FancyTextEditor import Button from './buttons/Button.tsx' import { openFileDialog } from '../utils/openFileDialog.ts' import makePica from 'pica' -import { useTranslations } from '../app/i18n/useTranslations.ts' +import { useTranslations } from '../app/i18n/translations.ts' interface NewPostWidgetProps { onSubmit: ( From 1710d5d91d577470a62134da0e8228b2db6d2296 Mon Sep 17 00:00:00 2001 From: john Date: Tue, 17 Jun 2025 09:44:30 +0200 Subject: [PATCH 10/39] remove email from authcodes --- .../subpages/SignupCodesManagementPage.tsx | 29 ++++--------------- src/app/auth/authService.ts | 11 +++++-- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/src/app/admin/pages/subpages/SignupCodesManagementPage.tsx b/src/app/admin/pages/subpages/SignupCodesManagementPage.tsx index 4dfdfcb..0075c73 100644 --- a/src/app/admin/pages/subpages/SignupCodesManagementPage.tsx +++ b/src/app/admin/pages/subpages/SignupCodesManagementPage.tsx @@ -1,5 +1,5 @@ import { AuthService } from '../../../auth/authService.ts' -import { useEffect, useState, useRef, MouseEvent } from 'react' +import { useEffect, useState, useRef, MouseEvent, useCallback } from 'react' import { SignupCode } from '../../../auth/signupCode.ts' import { Temporal } from '@js-temporal/polyfill' import Button from '../../../../components/buttons/Button.tsx' @@ -12,25 +12,24 @@ export default function SignupCodesManagementPage({ authService }: SignupCodesMa const [codes, setCodes] = useState([]) const [code, setCode] = useState('') const [name, setName] = useState('') - const [email, setEmail] = useState('') const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState(null) const dialogRef = useRef(null) const [tooltipPosition, setTooltipPosition] = useState<{ x: number; y: number } | null>(null) const [activeCode, setActiveCode] = useState(null) - const fetchCodes = async () => { + const fetchCodes = useCallback(async () => { try { setCodes(await authService.listSignupCodes()) } catch (err) { console.error('Failed to fetch signup codes:', err) } - } + }, [authService]) useEffect(() => { const timeoutId = setTimeout(fetchCodes) return () => clearTimeout(timeoutId) - }, [authService]) + }, [authService, fetchCodes]) const handleCreateCode = async (e: React.FormEvent) => { e.preventDefault() @@ -38,12 +37,11 @@ export default function SignupCodesManagementPage({ authService }: SignupCodesMa setError(null) try { - await authService.createSignupCode(code, email, name) + await authService.createSignupCode(code, name) setCode('') setName('') - setEmail('') dialogRef.current?.close() - fetchCodes() // Refresh the table + fetchCodes() } catch (err) { setError(err instanceof Error ? err.message : 'Failed to create signup code') } finally { @@ -116,7 +114,6 @@ export default function SignupCodesManagementPage({ authService }: SignupCodesMa Code - Email Redeemed By Expires On @@ -134,7 +131,6 @@ export default function SignupCodesManagementPage({ authService }: SignupCodesMa {code.code} - {code.email} {code.redeemedBy || 'Not redeemed'} {formatDate(code.expiresOn)} @@ -191,19 +187,6 @@ export default function SignupCodesManagementPage({ authService }: SignupCodesMa /> -
- - setEmail(e.target.value)} - className="w-full px-3 py-2 border border-gray-300 rounded-md" - /> -
-
@@ -120,7 +120,7 @@ export default function LoginPage({ authService }: LoginPageProps) { {t('auth.login.register_instead')} - {error} + {error} From 1fc52a16b964229ef85d6ddf8d67c40d6902d92a Mon Sep 17 00:00:00 2001 From: john Date: Tue, 17 Jun 2025 09:57:01 +0200 Subject: [PATCH 14/39] v1.24.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2e81fa1..5542282 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "femto-webapp", "private": true, - "version": "1.23.0", + "version": "1.24.0", "type": "module", "scripts": { "dev": "vite --host 0.0.0.0", From c0d08897c1908c519543413aca0f19c1f9a25398 Mon Sep 17 00:00:00 2001 From: john Date: Tue, 17 Jun 2025 09:58:24 +0200 Subject: [PATCH 15/39] change api url --- scripts/bump-build-push.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/bump-build-push.sh b/scripts/bump-build-push.sh index ae679c6..45d88e4 100755 --- a/scripts/bump-build-push.sh +++ b/scripts/bump-build-push.sh @@ -8,8 +8,7 @@ USERNAME="johnbotris" IMAGE_NAME="femto-webapp" # Add this before the docker build line -export VITE_API_URL="https://femto-api.botris.social" - +export VITE_API_URL="https://api.botris.social" # Step 0: Ensure clean working directory if [[ -n $(git status --porcelain) ]]; then From 58a214444fbc17d38a3cc0f83de851d7417d5708 Mon Sep 17 00:00:00 2001 From: john Date: Tue, 17 Jun 2025 09:59:03 +0200 Subject: [PATCH 16/39] v1.25.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5542282..f826503 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "femto-webapp", "private": true, - "version": "1.24.0", + "version": "1.25.0", "type": "module", "scripts": { "dev": "vite --host 0.0.0.0", From df0a145f3bb4f9d921243fe42eae0e6f10fd2338 Mon Sep 17 00:00:00 2001 From: john Date: Tue, 17 Jun 2025 10:09:19 +0200 Subject: [PATCH 17/39] add deployment to build script --- scripts/bump-build-push.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/scripts/bump-build-push.sh b/scripts/bump-build-push.sh index 45d88e4..42c2d57 100755 --- a/scripts/bump-build-push.sh +++ b/scripts/bump-build-push.sh @@ -2,6 +2,15 @@ set -euo pipefail +# Parse command line arguments +DEPLOY=false +for arg in "$@"; do + case "$arg" in + -d|--deploy) DEPLOY=true ;; + *) echo "Unknown option: $arg"; echo "Usage: $0 [-d|--deploy]"; exit 1 ;; + esac +done + # CONFIGURATION REGISTRY="docker.botris.dev" USERNAME="johnbotris" @@ -54,3 +63,10 @@ git push origin main git push origin "v$NEW_VERSION" echo "🎉 Release v$NEW_VERSION complete." + +# Step 6: Deploy if flag is set +if [ "$DEPLOY" = true ]; then + echo "🚀 Deploying to production..." + ssh john@botris.social 'bash /home/john/docker/femto/update.sh' + echo "✅ Deployment complete." +fi From f21d20e08c44d0450bdd52cf38cbcff6e0d2eac6 Mon Sep 17 00:00:00 2001 From: john Date: Tue, 17 Jun 2025 10:30:54 +0200 Subject: [PATCH 18/39] niceify build script --- package.json | 2 +- scripts/{bump-build-push.sh => publish.sh} | 35 ++++++++++++++++++++-- 2 files changed, 33 insertions(+), 4 deletions(-) rename scripts/{bump-build-push.sh => publish.sh} (58%) diff --git a/package.json b/package.json index f826503..e89b605 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "build": "tsc -b && vite build", "lint": "eslint .", "preview": "vite preview", - "build:deploy": "bash scripts/bump-build-push.sh", + "build:deploy": "bash scripts/publish.sh", "generate:schema": "node scripts/generate-schema.mjs" }, "dependencies": { diff --git a/scripts/bump-build-push.sh b/scripts/publish.sh similarity index 58% rename from scripts/bump-build-push.sh rename to scripts/publish.sh index 42c2d57..fbe77c7 100755 --- a/scripts/bump-build-push.sh +++ b/scripts/publish.sh @@ -2,12 +2,41 @@ set -euo pipefail +# Function to display help text +show_help() { + echo "Usage: $0 [OPTIONS]" + echo + echo "Description:" + echo " This script automates the process of bumping the version, building a Docker image," + echo " pushing it to the registry, and optionally deploying to production." + echo + echo "Options:" + echo " -h, --help Display this help message and exit" + echo " -d, --deploy Deploy to production after building and pushing" + echo " --major Bump the major version (x.0.0)" + echo " --minor Bump the minor version (0.x.0)" + echo " --patch Bump the patch version (0.0.x) [default]" + echo + echo "Examples:" + echo " $0 # Bump patch version, build and push" + echo " $0 --minor # Bump minor version, build and push" + echo " $0 --major -d # Bump major version, build, push and deploy" + echo " $0 --patch --deploy # Bump patch version, build, push and deploy" + echo +} + # Parse command line arguments DEPLOY=false +VERSION_TYPE="patch" # Default to patch version bump + for arg in "$@"; do case "$arg" in + -h|--help) show_help; exit 0 ;; -d|--deploy) DEPLOY=true ;; - *) echo "Unknown option: $arg"; echo "Usage: $0 [-d|--deploy]"; exit 1 ;; + --major) VERSION_TYPE="major" ;; + --minor) VERSION_TYPE="minor" ;; + --patch) VERSION_TYPE="patch" ;; + *) echo "Unknown option: $arg"; echo "Usage: $0 [-h|--help] [-d|--deploy] [--major|--minor|--patch]"; exit 1 ;; esac done @@ -30,8 +59,8 @@ OLD_VERSION=$(node -p "require('./package.json').version") echo "🔍 Current version: $OLD_VERSION" # Step 2: Bump version without Git tag/commit -echo "🚀 Bumping minor version..." -yarn version --minor --no-git-tag-version +echo "🚀 Bumping $VERSION_TYPE version..." +yarn version --$VERSION_TYPE --no-git-tag-version NEW_VERSION=$(node -p "require('./package.json').version") echo "📦 New version: $NEW_VERSION" From f7771c7df36c5cadd4268d70e911ba8d709ca846 Mon Sep 17 00:00:00 2001 From: john Date: Tue, 17 Jun 2025 10:47:46 +0200 Subject: [PATCH 19/39] tweak help text --- scripts/publish.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/publish.sh b/scripts/publish.sh index fbe77c7..68a75e4 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -11,8 +11,8 @@ show_help() { echo " pushing it to the registry, and optionally deploying to production." echo echo "Options:" - echo " -h, --help Display this help message and exit" - echo " -d, --deploy Deploy to production after building and pushing" + echo " -h,--help Display this help message and exit" + echo " -d,--deploy Deploy to production after building and pushing" echo " --major Bump the major version (x.0.0)" echo " --minor Bump the minor version (0.x.0)" echo " --patch Bump the patch version (0.0.x) [default]" From 7fab3d0d9f40f44f965c79ac90b1722618b2ff00 Mon Sep 17 00:00:00 2001 From: john Date: Tue, 17 Jun 2025 11:18:23 +0200 Subject: [PATCH 20/39] add source code link --- public/forgejo-logo-primary.svg | 40 +++++++++++++++++++++++++++++++++ src/components/NavBar.tsx | 23 ++++++++++++++++--- 2 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 public/forgejo-logo-primary.svg diff --git a/public/forgejo-logo-primary.svg b/public/forgejo-logo-primary.svg new file mode 100644 index 0000000..7f64c1a --- /dev/null +++ b/public/forgejo-logo-primary.svg @@ -0,0 +1,40 @@ + + + + + Forgejo logo + Caesar Schinas + + + + + + + + + + + + + diff --git a/src/components/NavBar.tsx b/src/components/NavBar.tsx index 02baea5..330c7aa 100644 --- a/src/components/NavBar.tsx +++ b/src/components/NavBar.tsx @@ -8,9 +8,26 @@ export default function NavBar({ children }: PropsWithChildren) { const user = useUser() const isSuperUser = user?.roles.includes(Role.SuperUser) return ( -