<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Webdevelopment Archives - Mobile USTP MKL</title>
	<atom:link href="https://mobile.fhstp.ac.at/category/development/webdevelopment/feed/" rel="self" type="application/rss+xml" />
	<link>https://mobile.fhstp.ac.at/category/development/webdevelopment/</link>
	<description>Die &#34;Mobile Forschungsgruppe&#34; der USTP, sie  sammelt hier alles zu den Themen Design, UX und Entwicklung mobiler Applikationen</description>
	<lastBuildDate>Thu, 17 Sep 2026 09:37:10 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://mobile.fhstp.ac.at/wp-content/uploads/2025/03/icon-120x120.webp</url>
	<title>Webdevelopment Archives - Mobile USTP MKL</title>
	<link>https://mobile.fhstp.ac.at/category/development/webdevelopment/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>OnTime V2</title>
		<link>https://mobile.fhstp.ac.at/allgemein/ontime-v2/</link>
		
		<dc:creator><![CDATA[Sander Hahn]]></dc:creator>
		<pubDate>Thu, 17 Sep 2026 09:37:09 +0000</pubDate>
				<category><![CDATA[Allgemein]]></category>
		<category><![CDATA[Cross Plattform]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Projekte]]></category>
		<category><![CDATA[Webdevelopment]]></category>
		<category><![CDATA[database]]></category>
		<category><![CDATA[design]]></category>
		<category><![CDATA[React]]></category>
		<category><![CDATA[Rust]]></category>
		<category><![CDATA[supabase]]></category>
		<category><![CDATA[Typescript]]></category>
		<guid isPermaLink="false">https://mobile.fhstp.ac.at/?p=15971</guid>

					<description><![CDATA[<p>OnTime started because I didn&#8217;t want to pay for a time tracker. Version one only had that traccker and one Tauri 2 codebase that shipped to the web, Windows and Android. V2 lives in a new repository, but it is of course built on Version one. The tracker is still at its core and around <a class="read-more" href="https://mobile.fhstp.ac.at/allgemein/ontime-v2/">[...]</a></p>
<p>The post <a href="https://mobile.fhstp.ac.at/allgemein/ontime-v2/">OnTime V2</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">OnTime started because I didn&#8217;t want to pay for a time tracker. Version one only had that traccker and one Tauri 2 codebase that shipped to the web, Windows and Android.</p>



<p class="wp-block-paragraph">V2 lives in a new repository, but it is of course built on Version one. The tracker is still at its core and around it, OnTime now handles teams, invoices and payments.</p>



<h2 class="wp-block-heading">What changed</h2>



<p class="wp-block-paragraph">In V1, one person tracked their own time but in V2, everything happens inside <strong>workspaces</strong>. A workspace can consist of just you or a whole team and every member has one of four roles: owner, admin, manager or member.</p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1540" height="800" src="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Calendar-Dark-1540x800.png" alt="" class="wp-image-15973" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Calendar-Dark-1540x800.png 1540w, https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Calendar-Dark-770x400.png 770w" sizes="(max-width: 1540px) 100vw, 1540px" /></figure>



<p class="wp-block-paragraph">But the basics are of course still there: a timer, manual entries and clients, projects and tasks with their own detail pages. The calendar is now shared and even updates live across devices. Projects can have budgets that send a notification when they cross a threshold. The whole app is in English and German and on mobile it works offline thanks to a persisted query cache.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1540" height="800" src="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Overview-Dark-1540x800.png" alt="" class="wp-image-15974" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Overview-Dark-1540x800.png 1540w, https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Overview-Dark-770x400.png 770w" sizes="(max-width: 1540px) 100vw, 1540px" /></figure>



<h2 class="wp-block-heading">Invoices and payments<br></h2>



<p class="wp-block-paragraph">This is where V2 changes the most from its previous version. Billable time becomes an invoice and when you send it, an Edge Function renders it as a PDF and <strong>freezes</strong> it. If you edit the underlying time entries later, the document the client already received doesn&#8217;t change.</p>



<p class="wp-block-paragraph">Additionally, OnTime connects to your bank through <strong>Enable Banking</strong>. The connection is read-only by design and you bring your own credentials, which are stored encrypted in Supabase Vault. A scheduled pg_cron job pulls in transactions and matches them against open invoices. When a match is confident, the invoice is marked paid automatically. Everything else waits in a queue for one-click review.<br></p>



<h2 class="wp-block-heading">Database changes</h2>



<p class="wp-block-paragraph">V1&#8217;s database only had to serve one person tracking their own hours. V2 needed a real data model, so I didn&#8217;t patch the old schema and started from the start.</p>



<p class="wp-block-paragraph">As said before, <strong>Everything belongs to a workspace</strong>: Clients, projects, tasks, time entries and invoices all hang off a workspace and every row knows which workspace it belongs to. That one change is what makes teams possible. It&#8217;s also what the security model is built on.</p>



<p class="wp-block-paragraph">Also, every table has row-level security policies that check the caller&#8217;s workspace and role before returning a single row. Views run with <code>security_invoker</code>, so they apply the permissions of the person querying them instead of quietly bypassing them. Owners, admins, managers and members all hit the same tables and get different answers. Since the frontend isn&#8217;t trusted to enforce any of this, pgTAP suites test the rules against a real database.</p>



<p class="wp-block-paragraph">Another security measure was to add a timestamp to entries when they get deleted. Now they are moved to a trash view, where they can be restored. A pg_cron job purges them for good once they&#8217;ve been in the trash long enough, so nobody has to clean up by hand.</p>



<p class="wp-block-paragraph">An invoice can&#8217;t point at time entries and hourly rates, because those can change or be edited. When an invoice is sent, it stores a JSONB snapshot of everything it was built from and the PDF is frozen alongside it. Rates also keep a history, so billing a project in March uses March&#8217;s rate even if it went up in June. Invoice numbers are generated per workspace and restart each year.</p>



<p class="wp-block-paragraph">pg_cron also triggers the bank sync so that transactions come in on a schedule, get matched against open invoices and confident matches mark the invoice as paid. The bank credentials never sit in a plain column and are encrypted in Supabase Vault.</p>



<p class="wp-block-paragraph">The shared calendar and the notifications subscribe to database changes through Supabase Realtime. When someone stops a timer on their phone, the entry shows up on everyone else&#8217;s desktop without a refresh.</p>



<h2 class="wp-block-heading">Testing</h2>



<p class="wp-block-paragraph">Tests run in three layers:</p>



<ul class="wp-block-list">
<li><strong>Vitest</strong> for units and components</li>



<li><strong>Playwright</strong> end-to-end tests against a preview build</li>



<li><strong>pgTAP</strong> for the security rules</li>
</ul>



<p class="wp-block-paragraph">Unit and end-to-end coverage are combined into one number and uploaded to Codecov on every CI run. Right now I have about 40% coverage but that will still be improved in later versions.</p>



<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-large"><img decoding="async" width="1540" height="800" data-id="15980" src="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Calendar-Dark-1-1540x800.png" alt="" class="wp-image-15980" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Calendar-Dark-1-1540x800.png 1540w, https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Calendar-Dark-1-770x400.png 770w" sizes="(max-width: 1540px) 100vw, 1540px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1540" height="800" data-id="15978" src="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Calendar-Light-1540x800.png" alt="" class="wp-image-15978" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Calendar-Light-1540x800.png 1540w, https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Calendar-Light-770x400.png 770w" sizes="auto, (max-width: 1540px) 100vw, 1540px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1540" height="800" data-id="15983" src="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Invoice-Detail-Dark-1540x800.png" alt="" class="wp-image-15983" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Invoice-Detail-Dark-1540x800.png 1540w, https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Invoice-Detail-Dark-770x400.png 770w" sizes="auto, (max-width: 1540px) 100vw, 1540px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1540" height="800" data-id="15985" src="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Invoice-Detail-Light-1540x800.png" alt="" class="wp-image-15985" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Invoice-Detail-Light-1540x800.png 1540w, https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Invoice-Detail-Light-770x400.png 770w" sizes="auto, (max-width: 1540px) 100vw, 1540px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1540" height="800" data-id="15984" src="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Invoices-Dark-1540x800.png" alt="" class="wp-image-15984" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Invoices-Dark-1540x800.png 1540w, https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Invoices-Dark-770x400.png 770w" sizes="auto, (max-width: 1540px) 100vw, 1540px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1540" height="800" data-id="15982" src="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Invoices-Light-1540x800.png" alt="" class="wp-image-15982" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Invoices-Light-1540x800.png 1540w, https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Invoices-Light-770x400.png 770w" sizes="auto, (max-width: 1540px) 100vw, 1540px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1540" height="800" data-id="15981" src="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Overview-Light-1540x800.png" alt="" class="wp-image-15981" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Overview-Light-1540x800.png 1540w, https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Overview-Light-770x400.png 770w" sizes="auto, (max-width: 1540px) 100vw, 1540px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1540" height="680" data-id="15977" src="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Payment-Review-Dark-1-1540x680.png" alt="" class="wp-image-15977"/></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1540" height="680" data-id="15979" src="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Payment-Review-Light-1540x680.png" alt="" class="wp-image-15979"/></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1540" height="800" data-id="15987" src="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Projects-Dark-1540x800.png" alt="" class="wp-image-15987" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Projects-Dark-1540x800.png 1540w, https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Projects-Dark-770x400.png 770w" sizes="auto, (max-width: 1540px) 100vw, 1540px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1540" height="800" data-id="15988" src="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Projects-Light-1540x800.png" alt="" class="wp-image-15988" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Projects-Light-1540x800.png 1540w, https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/OnTimeV2-Projects-Light-770x400.png 770w" sizes="auto, (max-width: 1540px) 100vw, 1540px" /></figure>
</figure>
<p>The post <a href="https://mobile.fhstp.ac.at/allgemein/ontime-v2/">OnTime V2</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>RevMatchMarket Phase 2 – From Swipe-Based Browsing to Buyer-Seller Communication</title>
		<link>https://mobile.fhstp.ac.at/allgemein/revmatchmarket-phase-2-from-swipe-based-browsing-to-buyer-seller-communication/</link>
		
		<dc:creator><![CDATA[Csongor]]></dc:creator>
		<pubDate>Wed, 16 Sep 2026 21:40:10 +0000</pubDate>
				<category><![CDATA[Allgemein]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Webdevelopment]]></category>
		<category><![CDATA[mobile]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[Web-App]]></category>
		<guid isPermaLink="false">https://mobile.fhstp.ac.at/?p=15917</guid>

					<description><![CDATA[<p>1. Introduction Rev Match Market is a used car marketplace lead by the left right swiping mechanism for marking interest in used car postings and connecting buyers with sellers. This blog post describes the second phase of development that took place on this project. Initially a MVP was made in the 1st phase that already <a class="read-more" href="https://mobile.fhstp.ac.at/allgemein/revmatchmarket-phase-2-from-swipe-based-browsing-to-buyer-seller-communication/">[...]</a></p>
<p>The post <a href="https://mobile.fhstp.ac.at/allgemein/revmatchmarket-phase-2-from-swipe-based-browsing-to-buyer-seller-communication/">RevMatchMarket Phase 2 – From Swipe-Based Browsing to Buyer-Seller Communication</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">1. Introduction</h2>



<p class="wp-block-paragraph">Rev Match Market is a used car marketplace lead by the left right swiping mechanism for marking interest in used car postings and connecting buyers with sellers. This blog post describes the second phase of development that took place on this project. Initially a MVP was made in the 1st phase that already contained the following features: authenticated sessions, swipe-based discovery, favorites, and seller listing management. For further reading about the 1st phase find the previous blog post <a href="https://mobile.fhstp.ac.at/allgemein/rev-match-market-a-swipe-based-used-car-marketplace/">here</a>.</p>



<p class="wp-block-paragraph">As a continuation to the initial MVP, the 2nd phase&#8217;s main goal was to introduce buyer-seller communication within the application aided with a notification system informing buyers and sellers about new interactions from each other. Another goal was to significantly improve the mobile experience and expand the capability of the UI from a single mobile view to a responsively transitioning UI that supports desktop, tablet, and mobile. Additional goals were the implementation of a concrete sold state that could later be the starting point of KPI tracking. Finally, to be able to later experiment with the app as a real business the application structure and code quality was to be made more production-ready.</p>



<h2 class="wp-block-heading">2. From Phase 1 MVP to Phase 2 &#8211; The Plan</h2>



<p class="wp-block-paragraph">The starting point of the 2nd phase was directly from where phase 1 and the last blog post left off.</p>



<ul class="wp-block-list">
<li>Authenticated marketplace.</li>



<li>Seller listing creation, editing and soft deletion.</li>



<li>Swipe-based discovery with LIKE and PASS.</li>



<li>Favorites and basic make/model/price filtering.</li>



<li>Nuxt, NestJS, PostgreSQL and Prisma foundation.</li>
</ul>



<p class="wp-block-paragraph">The features implemented by phase 2 in order of priority.</p>



<ul class="wp-block-list">
<li>Mobile-first redesign before implementing major new screens.</li>



<li>Chat and in-app notifications as the main new product features.</li>



<li>Improved marketplace filtering and incremental feed loading.</li>



<li>Cleaner domain boundaries, response contracts and privacy handling.</li>
</ul>



<p class="wp-block-paragraph">The result speak for them self, even if at this point of the blog only visually.</p>



<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-2 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-large size-full"><img loading="lazy" decoding="async" width="1699" height="944" data-id="16003" src="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/Screenshot-2026-09-16-131945-3.png" alt="" class="wp-image-16003" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/Screenshot-2026-09-16-131945-3.png 1699w, https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/Screenshot-2026-09-16-131945-3-1536x853.png 1536w" sizes="auto, (max-width: 1699px) 100vw, 1699px" /></figure>
</figure>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-8f761849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:100%"></div>
</div>



<h2 class="wp-block-heading">3. The Mobile-First Interface</h2>



<p class="wp-block-paragraph"><strong>Shared Application Shell</strong><br>The shell places the screens in a wrapping element which provides consistent navigation throughout the navigation between pages and also provides a responsive layout. The components and visual primitives used in the application are also standardized across the different pages including spacing, typography and interaction states. This meant that many components from phase 1 have been completely replaced with special care so no parallel or redundant designs would have to be maintained.</p>



<p class="wp-block-paragraph"><strong>Screen Changes and Account Identity</strong><br>To start off at the beginning of users journey the login and registration pages have been completely redesigned and made responsive, along with the swipe, car-detail, favorites and seller-listing screens. A visible addition to the users was also made by displaying a name for user facing identity. The user identity also gained increased protection, since emails and user IDs of other users are no longer reaching the frontend other than public contact information on car-detail pages. </p>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="545" height="1189" src="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/Screenshot-2026-09-16-132131.png" alt="" class="wp-image-15934" style="aspect-ratio:0.45837641540362645;width:192px;height:auto"/><figcaption class="wp-element-caption">Swipe page of the application with demo data where users brows listings.</figcaption></figure>



<h2 class="wp-block-heading"><strong>4. Improving Marketplace Discovery</strong></h2>



<p class="wp-block-paragraph">A task originally coming from phase 1 has been implemented and improved, which is pagination and filtering of the queue of cars on the swipe page. First of all Filtering did already exist but it is now moved from the frontend to the backend. The number of filters remains small though now its way easier to expand. The front end now only holds a limited number of cars that come next and before the queue is exhausted the prefetching of new cars occurs. Pass decisions still expire and listings return to the queue later.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="3760" height="3160" src="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/image-1.png" alt="" class="wp-image-16005" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/image-1.png 3760w, https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/image-1-1536x1291.png 1536w, https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/image-1-2048x1721.png 2048w" sizes="auto, (max-width: 3760px) 100vw, 3760px" /></figure>



<h2 class="wp-block-heading">5. Communication (Chat), Notifications and Sale Lifecycle</h2>



<p class="wp-block-paragraph">This section is arguably the most important for phase 2. This encapsulates Likes creating conversations, the chat experience between buyer and seller, in-app notifications, and a new state for listings &#8220;SOLD&#8221;.</p>



<p class="wp-block-paragraph"><strong>LIKEs and conversations</strong><br>As one can know from phase 1 when a user likes a listing, the listing appears in the users favorites tab. Now following that the seller also receives a notification of a potential buyer who liked their listing. To avoid the creation of empty conversations a like in it self doesn&#8217;t create a new conversation. Following the like both parties get the chance to initiate a conversation. The seller can initiate from the notification they receive and the buyer can start a chat from the favorites tab or the car-detail page. Either way a new conversation is created when the first message is sent. There can only be one conversation per car and interested buyer.</p>



<p class="wp-block-paragraph"><strong>Chat experience</strong><br>Chats appear in a list like page which provides basic information to the user about all the chats like total number of unread messages and about individual list items representing a single chat like the last message, the time it was sent and the car that is the subject of the conversation. Chats can still be reached following a successful sale or if a buyer decided to withdraw interest. When a listing is deleted the chat is still readable but new messages are blocked.</p>



<p class="wp-block-paragraph"><strong>In-app Notifications</strong><br>The two sources of notifications are the Likes and messages. When the application is open and the user receives a notification an in-app popup appears temporarily. In case the user misses the popup, they might still access the notifications in their inbox with a list view. The notification inbox list view similarly to the chat shows notification contents, read or unread states and a counter for all unread notifications. The user can mark only one notification or all at the same time as read.</p>



<p class="wp-block-paragraph"><strong>Sold Listing</strong><br>The listings can be marked as sold only by the seller. They can do this either from the listing itself or the conversation which will associate the conversation with the successful sale. Sold listings can’t be found on the swipe page anymore and buyers may no longer start new conversations about the sold listings. Within favorites or existing conversations, the listing shows up as sold.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="3760" height="3560" src="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/image.png" alt="" class="wp-image-16004" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/image.png 3760w, https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/image-1536x1454.png 1536w, https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/image-2048x1939.png 2048w" sizes="auto, (max-width: 3760px) 100vw, 3760px" /></figure>



<h2 class="wp-block-heading"><strong>6. Technical Foundation and Data Model</strong></h2>



<ul class="wp-block-list">
<li>RevMatchMarket still uses Nuxt 4 for the frontend, NestJS for the backend, and PostgreSQL with Prisma ORM for the database.</li>



<li>New persistence models that were added in phase 2 are conversations, messages, notifications, and sold state.</li>



<li>A major change is the increased privacy meaning that response DTOs no longer expose account email and userID.</li>



<li>The development process was also improved between phases 1 and 2 by using dedicated git feature branches merged into develop and later charry picking from develop to main (production).</li>
</ul>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="10460" height="3960" src="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/image-3.png" alt="" class="wp-image-16007" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/image-3.png 10460w, https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/image-3-1536x582.png 1536w, https://mobile.fhstp.ac.at/wp-content/uploads/2026/09/image-3-2048x775.png 2048w" sizes="auto, (max-width: 10460px) 100vw, 10460px" /></figure>



<h2 class="wp-block-heading"><strong>7. Challenges and Lessons Learned</strong></h2>



<p class="wp-block-paragraph">The first challenge was to plan the chat and notification system before implementation, what I learned is that it is significantly easier if the process is visualized beforehand using one of many flow chat methods. I also learned about the effort required to produce production grade code, where edge cases have to be treated with special care. I have learnt about several new techniques listed below.</p>



<p class="wp-block-paragraph"><strong>Cursor Pagination</strong><br>Pagination is used when requesting batches of a long list of data from the API. Cursor pagination loads the next group of records relative to the last record already received. This differs from the more simple offset pagination which requests by skipping x amount of records from the start. A cursor marks the last record loaded by a deterministic value in most cases a timestamp and the record&#8217;s id. This is used on RevMatchMarket&#8217;s swipe feed, and notification and chat inbox for example to avoid duplicate or skipped records.</p>



<p class="wp-block-paragraph"><strong>Client-side Reconciliation</strong><br>Reconciliation is to be understood as checking back with the API new data based on the data the browser already had. This process merges new server data with the existing state of the browser. This technique is important mainly because the application uses polling to fetch new messages or notifications. In case a notification is deleted and at the same time the polling occurs the deleted notification may appear again because the polling happened before the delete went through. When new server data arrives it is merged with the browser data by comparing IDs and the newer updatedAt wins over the older one.</p>



<p class="wp-block-paragraph"><strong>Prefetching</strong><br>The swipe page uses prefetching to achieve a continues feed. When only 3 cars remain from the previous batch of 20 a newer batch is fetched. To have a fluid swipe feed additional steps are also taken to filter out duplicates, potentially successful swipes can&#8217;t be re-introduced again, and account and filter changes are watched.</p>



<h2 class="wp-block-heading">8. Results</h2>



<p class="wp-block-paragraph">The development in the 2nd phase yielded the following results: a completely reworked design and application structure, the ability for buyers and sellers to connect within the application after a potential buyer liked a listing, a notification system to keep buyers and sellers up to date with liked and owned listings, and finally a revised discovery logic that is more reliable and scalable.</p>
<p>The post <a href="https://mobile.fhstp.ac.at/allgemein/revmatchmarket-phase-2-from-swipe-based-browsing-to-buyer-seller-communication/">RevMatchMarket Phase 2 – From Swipe-Based Browsing to Buyer-Seller Communication</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Workshop &#124; Astro</title>
		<link>https://mobile.fhstp.ac.at/workshop/workshop-astro/</link>
		
		<dc:creator><![CDATA[Caroline Labres]]></dc:creator>
		<pubDate>Sun, 16 Nov 2025 17:20:52 +0000</pubDate>
				<category><![CDATA[Webdevelopment]]></category>
		<category><![CDATA[Workshop]]></category>
		<category><![CDATA[Astro]]></category>
		<guid isPermaLink="false">https://mobile.fhstp.ac.at/?p=15111</guid>

					<description><![CDATA[<p>Im 3. Semester habe ich einen Workshop zu Astro gehalten. Dabei habe ich erklärt, was das Webframework ausmacht, und anhand von einem praktischen Beispiel die wichtigsten Kernfunktionen demonstriert. Was ist Astro? Astro ist ein Framework für contentfokussierte Webprojekte, wie z.B. Blogs, E-Commerce- oder Marketing-Websites. Es setzt auf eine komponentenbasierte Web-Architektur, was bedeutet, dass das meiste <a class="read-more" href="https://mobile.fhstp.ac.at/workshop/workshop-astro/">[...]</a></p>
<p>The post <a href="https://mobile.fhstp.ac.at/workshop/workshop-astro/">Workshop | Astro</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Im 3. Semester habe ich einen Workshop zu Astro gehalten. Dabei habe ich erklärt, was das Webframework ausmacht, und anhand von einem praktischen Beispiel die wichtigsten Kernfunktionen demonstriert.</p>



<h2 class="wp-block-heading">Was ist Astro?</h2>



<p class="wp-block-paragraph">Astro ist ein Framework für contentfokussierte Webprojekte, wie z.B. Blogs, E-Commerce- oder Marketing-Websites. Es setzt auf eine <strong>komponentenbasierte Web-Architektur</strong>, was bedeutet, dass das meiste als statische HTML-Seite gerendert wird und man kleine Islands mit JavaScript für Interaktivität hinzufügen kann. Astro verfolgt einen <strong>Server-first-Ansatz</strong> und kommt standardmäßig <strong>ohne JavaScript</strong> am Client aus, liefert aber bei Bedarf <strong>Unterstützung für Frameworks</strong> wie React, Vue, Svelte, Solid etc. Darüber hinaus bietet Astro <strong>Content Collections</strong>, mit denen sich Markdown-Inhalte strukturiert organisieren und mithilfe von TypeScript typsicher validieren lassen.</p>



<h2 class="wp-block-heading">Projekt erstellen</h2>



<p class="wp-block-paragraph"><code>npm create astro@latest</code> (basic, helpful starter Projekt auswählen)</p>



<p class="wp-block-paragraph">In VS Code gibt es eine Extension namens Astro, die hilfreich ist.</p>



<p class="wp-block-paragraph"> Ordnerstruktur (als Beispiel):</p>



<ul class="wp-block-list">
<li>assets</li>



<li>components</li>



<li>content (für Content Collections)</li>



<li>layouts</li>



<li>pages (file-based Routing)</li>



<li>styles</li>
</ul>



<h2 class="wp-block-heading">Dateiaufbau und Astro-Komponenten</h2>



<ul class="wp-block-list">
<li>Dateiendung: .astro</li>



<li>Codeausführung:
<ul class="wp-block-list">
<li>serverseitig: <code>--- ---</code> (Frontmatter)</li>



<li>clientseitig: <code>&lt;script>&lt;/script></code></li>
</ul>
</li>



<li>JSX-ähnliche Ausdrücke
<ul class="wp-block-list">
<li><code>{variable}</code> in HTML</li>



<li>Aber: Funktionen und Objekte können so nicht übergeben werden (wie z.B. bei React), stattdessen mit Script-Tag umsetzbar</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph">Beispielsweise MyAstroComp.astro:</p>



<pre class="wp-block-code"><code>---
// Script mit JS, das am Server ausgeführt wird
const text = "Hello World";
---
&lt;!-- Hier Template der Komponente mit HTML, CSS und JS --&gt;
&lt;div&gt;{text}&lt;/div&gt;</code></pre>



<p class="wp-block-paragraph">Was nicht funktioniert:</p>



<pre class="wp-block-code"><code>---
function handleClick() {
  console.log("clicked");
}
---
&lt;button onclick="handleClick()"&gt;{text}&lt;/button&gt;</code></pre>



<p class="wp-block-paragraph">Komponente in index.astro verwenden:</p>



<pre class="wp-block-code"><code>---
import MyAstroComp from "../components/MyAstroComp.astro";
---
&lt;MyAstroComp /&gt;</code></pre>



<ul class="wp-block-list">
<li><code>&lt;slot /></code> für Children (wie {children} in React)</li>



<li>Mit <code>Astro.props</code> auf Properties zugreifen</li>
</ul>



<pre class="wp-block-code"><code>---
interface Props {
  name: string;
}
const { name } = Astro.props;
---
&lt;h2&gt;Hello {name}!&lt;/h2&gt;</code></pre>



<h2 class="wp-block-heading">Unterschiede Astro vs. JSX</h2>



<figure class="wp-block-table"><table class="has-black-color has-text-color has-link-color has-fixed-layout"><thead><tr><th></th><th>Astro</th><th>JSX</th></tr></thead><tbody><tr><td>Attribute</td><td>kebab-case</td><td>camelCase</td></tr><tr><td>Mehrere Elemente</td><td>Ohne Parent zulässig</td><td>Nur in einem einzelnen &lt;div&gt; oder &lt;&gt; zulässig</td></tr><tr><td>Kommentare</td><td>HTML- und JS-Kommentare</td><td>JS-Kommentare</td></tr></tbody></table></figure>



<h2 class="wp-block-heading">React hinzufügen</h2>



<p class="wp-block-paragraph">Interaktivität in Astro Komponenten einzubauen ist mühsam. Um mit einem Buttonklick umgehen zu können, müsste man ein &lt;script&gt; Tag hinzufügen. Stattdessen kann man aber auch ein beliebiges anderes Framework zum Projekt hinzufügen und so auch beispielsweise React-Komponenten bauen und nutzen.</p>



<p class="wp-block-paragraph">Um React als Beispiel hinzuzufügen: <code>npx astro add react</code></p>



<p class="wp-block-paragraph">Achtung: Komponente wird by default nur am Server gerendert!</p>



<p class="wp-block-paragraph">Für Interaktivität braucht es Hydration und eine Client Directive.</p>



<h3 class="wp-block-heading">Client Directives</h3>



<p class="wp-block-paragraph">Die client:* Directive gibt an, wann das JavaScript an den Browser geschickt werden soll. Die Komponente wird zuerst am Server gerendert (außer bei client:only), dann wird das JS gesendet und somit wird die Komponte hydrated und interaktiv.</p>



<ul class="wp-block-list">
<li><code>client: load</code>
<ul class="wp-block-list">
<li>JS: sofort beim Laden der Seite</li>
</ul>
</li>



<li><code>client: idle</code>
<ul class="wp-block-list">
<li>JS: nach initialem Ladeprozess und wenn requestIdleCallback-Event ausgelöst wird</li>
</ul>
</li>



<li><code>client: visible / client:visible={{rootMargin}}</code>
<ul class="wp-block-list">
<li>JS: sobald Komponente bzw. angegebener Margin sichtbar ist</li>
</ul>
</li>



<li><code>client:media={string}</code>
<ul class="wp-block-list">
<li>JS: wenn CSS media query erfüllt wird (z.B. nur für bestimmte Bildschirmgrößen)</li>
</ul>
</li>



<li><code>client:only={string}</code>
<ul class="wp-block-list">
<li>Überspringt SSR und rendert Komponente nur am Client. Framework muss als String übergeben werden.</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph">Die Client Directive wird dort hinzufügt, wo die Komponente verwendet wird.</p>



<pre class="wp-block-code"><code>---
import MyReactButton from "../components/MyReactButton";
---
&lt;MyReactButton client:load /&gt;</code></pre>



<h2 class="wp-block-heading">Output Type für Build</h2>



<p class="wp-block-paragraph">In der astro.config Datei kann der Output Type festgelegt werden. Dieser bestimmt, wann die Seiten standardmäßig gerendert werden (beim Builden oder on demand). Das gilt für alle Seiten, außer bei denen eine Ausnahme festgelegt ist.</p>



<p class="wp-block-paragraph">In astro.config.mjs: <code>output: 'static'</code> oder <code>'server'</code></p>



<ul class="wp-block-list">
<li><code>static</code> (default): SSG, Seiten werden vorgerendert (<em>prerendered</em>); Ergebnis: statische Website</li>



<li><code>server</code>: SSR, Seiten werden on demand gerendert; Ergebnis: server-rendered Website</li>



<li>Für einzelne Seite ändern: <code>export const prerender = false</code>
<ul class="wp-block-list">
<li>wenn in config static: <code>false</code></li>



<li>wenn in config server: <code>true</code></li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph">Wichtig: Wenn eine Seite SSR verwendet, muss ein <strong>Server-Adapter</strong> (z.B. node) festgelegt werden: <code>npx astro add node</code></p>



<h2 class="wp-block-heading">Blog als Praxisbeispiel</h2>



<p class="wp-block-paragraph">Als Beispiel wird ein kleiner, einfacher Blog programmiert. Dieser beinhaltet zwei Seiten, wobei auf einer alle Einträge mit Bild, Datum und Titel aufgelistet werden (Übersichtsseite) und auf der anderen die Informationen des jeweiligen Eintrags stehen (Detailseite). Die einzelnen Blogbeiträge sollen in Form von Markdown-Dateien erstellt, bearbeitet und verwaltet werden können. Auf das Styling wird bei diesem Beispiel nicht eingegangen.</p>



<h3 class="wp-block-heading">Content Collection erstellen</h3>



<p class="wp-block-paragraph">Content Collections helfen dabei, Markdown-Dateien zu organisieren. Genau das wird für die Blogbeiträge benötigt. Um eine Collection zu erstellen, muss diese in src/content.config.ts definiert werden.</p>



<pre class="wp-block-code"><code>import { glob } from "astro/loaders";
import { defineCollection, z } from "astro:content";

const blog = defineCollection({
  loader: glob({ base: "./src/content/blog", pattern: "**/*.{md,mdx}" }),
  schema: () =&gt;
    z.object({
      title: z.string(),
      date: z.coerce.date(),
      img: z.string(),
    }),
});

export const collections = { blog };</code></pre>



<p class="wp-block-paragraph">Die Funktion <code>defineCollection()</code> verlangt dabei zwei Parameter, einerseits den Loader und andererseits optional das Schema. Der Loader legt fest, wie und von wo die Inhalte der Collection geladen werden. Astro stellt dabei zwei integrierte Varianten zur Verfügung:</p>



<ul class="wp-block-list">
<li><code>glob()</code>: erstellt Einträge aus Ordnern mit Dateien</li>



<li><code>file()</code>: erstellt Einträge basierend auf einer lokalen Datei (z.B. JSON-File)</li>
</ul>



<p class="wp-block-paragraph">Das Schema wird mit zod erstellt. Wird ein Schema definiert, so werden die entsprechenden Markdown-Dateien dahingehend validiert. Außerdem können generierte Types verwendet werden (z.B. <code>type Props = CollectionEntry&lt;"blog"&gt;["data"];</code>). Wichtig ist nur, dass der Dev-Server neu gestartet wird, wenn sich Änderungen am Schema ergeben, sodass Astro das mitbekommt.</p>



<p class="wp-block-paragraph">Ein Blogeintrag soll in diesem Fall immer einen Titel, ein Datum und ein Bild beinhalten, sodass diese Informationen auf der Übersichtsseite angezeigt und auf der Detailseite immer gleich formatiert werden können.</p>



<h3 class="wp-block-heading">Blogbeiträge hinzufügen</h3>



<p class="wp-block-paragraph">Es können jetzt diverse Blogbeiträge in Form von Markdown-Dateien unter content/blog erstellt werden (Pfad in content.config.ts festgelegt).</p>



<p class="wp-block-paragraph">Beispiel: first-entry.md</p>



<pre class="wp-block-code"><code>---
title: "First Entry"
date: "2025-10-30"
img: "/favicon.svg"
---
Hello, this is my first entry.

## H2

This is a paragraph.</code></pre>



<p class="wp-block-paragraph">Im Frontmatter (innerhalb der drei Bindestriche) werden die drei Parameter angegeben, darunter folgt der Inhalt. Als Bild wird der Einfachheit halber das favicon im public-Ordner hergenommen. Der Blogbeitrag kann Überschriften, Absätze, Links, Codeteile, Tabellen und alles, was Markdown zu bieten hat, beinhalten. Wichtig ist nur, dass man das entsprechende Styling dafür definiert (h2, p, code, a etc.).</p>



<h4 class="wp-block-heading">MDX</h4>



<p class="wp-block-paragraph">Es können nicht nur md-Dateien erstellt werden, sondern auch mdx-Dateien, in denen die eigenen Komponenten (egal ob Astro, React etc.) verwendet werden können. Es muss nur die entsprechende Astro-Integration installiert werden (<code>npx astro add mdx</code>) und mdx in der Definition der Content Collection berücksichtigt werden (wurde bereits getan).</p>



<p class="wp-block-paragraph">Beispiel: second-entry.mdx</p>



<pre class="wp-block-code"><code>---
title: "Second Entry"
date: "2025-10-31"
img: "/favicon.svg"
---

This is my second entry.

import MyReactButton from "../../components/MyReactButton"

&lt;MyReactButton client:load /&gt;</code></pre>



<p class="wp-block-paragraph">components/MyReactButton.tsx</p>



<pre class="wp-block-code"><code>export default function MyReactButton(){

    function handleClick(){
        console.log("Hello, you clicked me")
    }

    return (
        &lt;button onClick={handleClick}&gt;Click me&lt;/button&gt;
    )
}</code></pre>



<h3 class="wp-block-heading">Seiten erstellen</h3>



<p class="wp-block-paragraph">Astro verwendet file-based Routing. Die Ordner- und Dateistruktur im pages-Ordner bestimmt somit automatisch, unter welchen URLs die Seiten aufgerufen werden können. Im pages-Ordner wird ein Unterordner blog und darin index.astro und [slug].astro angelegt. Der Blog ist somit unter /blog erreichbar. index.astro ist eine statische Route, während [slug].astro eine dynamische Route ist. Der Parameter slug kann dabei durch eine beliebige andere Bezeichnung ersetzt werden. Für mehr Tiefe kann auch ein Rest-Parameter verwendet werden: [&#8230;path].astro. </p>



<h4 class="wp-block-heading">Übersichtsseite</h4>



<p class="wp-block-paragraph">Für die index Seite wird zuerst ein Layout erstellt. Ein Layout ist dabei nichts anderes als eine normale Astro-Komponente.</p>



<p class="wp-block-paragraph">layouts/Layout.astro</p>



<pre class="wp-block-code"><code>---<br>import "../styles/global.css";<br>---<br><br>&lt;!doctype html&gt;<br>&lt;html lang="en"&gt;<br>  &lt;head&gt;<br>    &lt;meta charset="UTF-8" /&gt;<br>    &lt;meta name="viewport" content="width=device-width" /&gt;<br>    &lt;link rel="icon" type="image/svg+xml" href="/favicon.svg" /&gt;<br>    &lt;meta name="generator" content={Astro.generator} /&gt;<br>    &lt;title&gt;My Blog&lt;/title&gt;<br>  &lt;/head&gt;<br>  &lt;body&gt;<br>    &lt;slot /&gt;<br>  &lt;/body&gt;<br>&lt;/html&gt;<br><br>&lt;style&gt;<br>  html,<br>  body {<br>    margin: 0;<br>    width: 100%;<br>    height: 100%;<br>  }<br>&lt;/style&gt;</code></pre>



<p class="wp-block-paragraph">Für den Header kann ebenso eine Astro-Komponente erstellt werden (components/Header.tsx).</p>



<pre class="wp-block-code"><code>&lt;header&gt;
  &lt;nav&gt;
    &lt;a href="/blog"&gt;My Blog&lt;/a&gt;
  &lt;/nav&gt;
&lt;/header&gt;
</code></pre>



<p class="wp-block-paragraph">Nun kann index.astro befüllt werden:</p>



<pre class="wp-block-code"><code>---
import { getCollection } from "astro:content";
import Header from "../../components/Header.astro";
import Layout from "../../layouts/Layout.astro";

const posts = (await getCollection("blog")).sort((a, b) =&gt; {
  return b.data.date.valueOf() - a.data.date.valueOf();
});
---

&lt;Layout&gt;
  &lt;Header /&gt;
  &lt;ul&gt;
    {
      posts.map((post) =&gt; (
        &lt;li&gt;
          &lt;a href={"/blog/" + post.id}&gt;
            &lt;div&gt;
              &lt;img src={post.data.img} /&gt;
            &lt;/div&gt;
            &lt;div&gt;{post.data.title}&lt;/div&gt;
            &lt;div&gt;{post.data.date.toLocaleDateString()}&lt;/div&gt;
          &lt;/a&gt;
        &lt;/li&gt;
      ))
    }
  &lt;/ul&gt;
&lt;/Layout&gt;</code></pre>



<p class="wp-block-paragraph">Mithilfe von <code>getCollection()</code> kann eine Collection, sprich ein Array von Einträgen, geholt werden. In diesem Beispiel werden die Einträge von der Blog-Collection zusätzlich nach dem Datum sortiert.</p>



<h4 class="wp-block-heading">Detailseite</h4>



<p class="wp-block-paragraph">Für die Detailseite wird ebenso ein Layout erstellt, das den Titel, das Bild und das Datum übergeben bekommt und anzeigt.</p>



<p class="wp-block-paragraph">layouts/BlogLayout.astro</p>



<pre class="wp-block-code"><code>---
import type { CollectionEntry } from "astro:content";
import Header from "../components/Header.astro";

type Props = CollectionEntry&lt;"blog"&gt;&#91;"data"];

const { title, date, img} = Astro.props;
---

&lt;!doctype html&gt;
&lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8" /&gt;
    &lt;meta name="viewport" content="width=device-width" /&gt;
    &lt;link rel="icon" type="image/svg+xml" href="/favicon.svg" /&gt;
    &lt;meta name="generator" content={Astro.generator} /&gt;
    &lt;title&gt;My Blog&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;Header /&gt;
    &lt;main&gt;
      &lt;article&gt;
        &lt;div&gt;
          &lt;img src={img} /&gt;
        &lt;/div&gt;
        &lt;div&gt;{date.toLocaleDateString()}&lt;/div&gt;
        &lt;h1&gt;{title}&lt;/h1&gt;
        &lt;div&gt;
          &lt;slot /&gt;
        &lt;/div&gt;
      &lt;/article&gt;
    &lt;/main&gt;
  &lt;/body&gt;
&lt;/html&gt;

&lt;style&gt;
  html,
  body {
    margin: 0;
    width: 100%;
    height: 100%;
  }
&lt;/style&gt;</code></pre>



<p class="wp-block-paragraph">Die Detailseite muss nun den Parameter slug auslesen und die entsprechenden Informationen dem BlogLayout übergeben. Es gibt zwei Varianten, das zu tun, je nachdem ob man SSR oder SSG verwenden möchte.</p>



<p class="wp-block-paragraph"><strong>Variante 1: SSR (on demand Rendering)</strong></p>



<pre class="wp-block-code"><code>---
import { render } from "astro:content";
import { getEntry } from "astro:content";
import BlogLayout from "../../layouts/BlogLayout.astro";

export const prerender = false;
const { slug } = Astro.params;

if (!slug) {
  return Astro.rewrite("/404");
}

const entry = await getEntry("blog", slug);

if (!entry) {
  return Astro.rewrite("/404");
}

const { Content } = await render(entry);
---

&lt;BlogLayout {...entry.data}&gt;
  &lt;Content /&gt;
&lt;/BlogLayout&gt;
</code></pre>



<p class="wp-block-paragraph">Mithilfe von <code>Astro.params</code> kann der Parameter slug ausgelesen werden. Mit <code>getEntry()</code> wird dann der entsprechende Eintrag geholt. Wenn es keinen passenden Eintrag gibt, wird 404 angezeigt. Um den Inhalt der Zielseite anzuzeigen, ohne die URL zu verändern, wird <code>Astro.rewrite()</code> genutzt.</p>



<p class="wp-block-paragraph"><strong>Variante 2: SSG (statische Seite)</strong></p>



<pre class="wp-block-code"><code>---
import type { GetStaticPaths } from "astro";
import { render } from "astro:content";
import BlogLayout from "../../layouts/BlogLayout.astro";
import { getCollection } from "astro:content";

export const getStaticPaths = (async () =&gt; {
  const entries = await getCollection("blog");

  return entries.map((entry) =&gt; ({
    params: { slug: entry.id },
    props: entry,
  }));
}) satisfies GetStaticPaths;

const entry = Astro.props;

const { Content } = await render(entry);
---

&lt;BlogLayout {...entry.data}&gt;
  &lt;Content /&gt;
&lt;/BlogLayout&gt;
</code></pre>



<p class="wp-block-paragraph">Soll eine dynamische Route statisch sein, so muss sie eine Funktion namens <code>getStaticPaths()</code> exportieren, die ein Array an Objekten mit dem property <code>params</code> zurückgibt. So werden alle möglichen Paths vordefiniert. In diesem Beispiel holt man sich also alle Einträge der Blog-Collection in der <code>getStaticPaths()</code>-Funktion und über die Properties (<code>Astro.props</code>) können dann die Informationen des einzelnen Eintrags ausgelesen werden.</p>



<h2 class="wp-block-heading">Fazit</h2>



<p class="wp-block-paragraph">Mit Astro können schnelle Websites erstellt werden. Wenn man bereits Erfahrung mit HTML, JSX oder React hat, so fällt einem der Einstieg in das Framework besonders leicht, da man viele vertraute Konzepte wiederfindet. Nur bei komplexeren Websites stößt Astro irgendwann auf seine Grenzen.</p>



<h2 class="wp-block-heading">Quelle</h2>



<p class="wp-block-paragraph"><a href="https://docs.astro.build/en">https://docs.astro.build/en</a></p>



<p class="wp-block-paragraph"></p>
<p>The post <a href="https://mobile.fhstp.ac.at/workshop/workshop-astro/">Workshop | Astro</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>PrintToMobile &#124; TreeFund</title>
		<link>https://mobile.fhstp.ac.at/development/printtomobile-treefund/</link>
		
		<dc:creator><![CDATA[Sander Hahn]]></dc:creator>
		<pubDate>Fri, 17 Oct 2025 12:56:35 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Webdevelopment]]></category>
		<category><![CDATA[mobile]]></category>
		<category><![CDATA[Print-to-mobile]]></category>
		<category><![CDATA[React]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[Webentwicklung]]></category>
		<guid isPermaLink="false">https://mobile.fhstp.ac.at/?p=14942</guid>

					<description><![CDATA[<p>Today’s world is becoming increasingly complicated – and increasingly grey. Pollution, conflict, and overconsumption are draining the color from our planet. The idea of TreeFund is simple: find an easy, meaningful way to help make our world greener again – one small step at a time. Idea The goal of TreeFund is to provide an <a class="read-more" href="https://mobile.fhstp.ac.at/development/printtomobile-treefund/">[...]</a></p>
<p>The post <a href="https://mobile.fhstp.ac.at/development/printtomobile-treefund/">PrintToMobile | TreeFund</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Today’s world is becoming increasingly complicated – and increasingly grey. Pollution, conflict, and overconsumption are draining the color from our planet. The idea of TreeFund is simple: find an easy, meaningful way to help make our world greener again – one small step at a time.</p>



<h2 class="wp-block-heading">Idea</h2>



<p class="wp-block-paragraph">The goal of <em>TreeFund</em> is to provide an easy and transparent way for people to contribute to environmental restoration projects in St. Pölten. Its main focus is supporting tree planting initiatives and teaching new generations how to help our planet. The idea is to connect with individuals who want to make a difference and focus on their specific needs. People will first encounter <em>TreeFund </em>through posters and flyers placed around the city. A QR code on each will lead directly to the <em>TreeFund </em>website, where users can learn more, get involved, and track the impact of their contributions.</p>



<p class="wp-block-paragraph"></p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-8f761849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-3 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="595" height="842" data-id="14964" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Print2-5.jpg" alt="" class="wp-image-14964"/></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="720" height="1520" data-id="14963" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-134357_Camera-5.jpg" alt="" class="wp-image-14963"/></figure>
</figure>
</div>
</div>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading"><strong>Implementation</strong></h2>



<p class="wp-block-paragraph">As the first step in the implementation process, I created a user persona: <em>Lisa Gruber</em>, a student at the <em>University of Applied Sciences St. Pölten</em>.</p>



<p class="wp-block-paragraph"><em>Lisa </em>is deeply environmentally conscious and eager to contribute to positive change. However, she often feels frustrated by the impersonal nature of many existing donation platforms. She wants to see the real impact of her actions and connect with initiatives that align with her values on a local level.</p>



<p class="wp-block-paragraph"></p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1787" height="1725" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Persona.png" alt="" class="wp-image-14966" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Persona.png 1787w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Persona-1536x1483.png 1536w" sizes="auto, (max-width: 1787px) 100vw, 1787px" /></figure>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">To personalize this process, the user (Lisa) first contacts TreeFund via a form describing their personal needs and where they want to see a part of the world a little greener. Only after the user is happy and the tree planting effort is accepted by the city, the payment process begins.&nbsp;</p>



<p class="wp-block-paragraph">To attract younger customers the design follows a minimalist aesthetic with clean whites and greens to emphasize the simplicity and positivity of the project’s goal. The design was first created in Figma with inspirations like Ecosia and TreeApp. My goal was to stick to a strict number of TailwindCSS colors. The website switches between Stone-50 and -100 for the background and Lime-400, -500, -600 and -800 for the green accents.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="387" height="144" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Frame-17.png" alt="" class="wp-image-14982"/></figure>
</div>


<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">After the design process, the actual website was implemented using React, Next.js, Tailwind CSS, and Lucide for icons.</p>



<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-4 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1436" height="2724" data-id="14975" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-135253_Chrome-1.jpg" alt="" class="wp-image-14975" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-135253_Chrome-1.jpg 1436w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-135253_Chrome-1-810x1536.jpg 810w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-135253_Chrome-1-1080x2048.jpg 1080w" sizes="auto, (max-width: 1436px) 100vw, 1436px" /></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1440" height="2724" data-id="14976" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-135326_Chrome-1.jpg" alt="" class="wp-image-14976" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-135326_Chrome-1.jpg 1440w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-135326_Chrome-1-812x1536.jpg 812w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-135326_Chrome-1-1083x2048.jpg 1083w" sizes="auto, (max-width: 1440px) 100vw, 1440px" /></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1440" height="2920" data-id="14974" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-135332_Chrome.jpg" alt="" class="wp-image-14974" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-135332_Chrome.jpg 1440w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-135332_Chrome-757x1536.jpg 757w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-135332_Chrome-1010x2048.jpg 1010w" sizes="auto, (max-width: 1440px) 100vw, 1440px" /></figure>
</figure>



<p class="wp-block-paragraph"><strong>Link to the Web Application:</strong> https://fhstp-print2mobil.vercel.app/</p>



<p class="wp-block-paragraph"><br><strong>Disclaimer: </strong><em>This is a personal project created for educational and illustrative purposes. It is not affiliated with any existing environmental organizations. The aim is to demonstrate how web technology can be used to promote sustainability and environmental awareness.</em></p>
<p>The post <a href="https://mobile.fhstp.ac.at/development/printtomobile-treefund/">PrintToMobile | TreeFund</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Print2Mobile &#124; AR &#8211; Web Application to project 3D Objects onto a Catalog</title>
		<link>https://mobile.fhstp.ac.at/development/webdevelopment/print2mobile-ar-web-application-to-project-3d-objects-onto-a-catalog/</link>
		
		<dc:creator><![CDATA[Jakob Laschober]]></dc:creator>
		<pubDate>Thu, 16 Oct 2025 12:43:41 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Webdevelopment]]></category>
		<category><![CDATA[AR]]></category>
		<category><![CDATA[Augmented Reality]]></category>
		<category><![CDATA[mobile]]></category>
		<category><![CDATA[mobile web]]></category>
		<category><![CDATA[Print-to-mobile]]></category>
		<category><![CDATA[Print2Mobile]]></category>
		<category><![CDATA[webdevelopment]]></category>
		<guid isPermaLink="false">https://mobile.fhstp.ac.at/?p=14874</guid>

					<description><![CDATA[<p>Have you ever seen a cool piece of furniture in a furniture store catalog? Maybe you wondered how the couch looks from behind, or how to view the table in 3D to better estimate its dimensions. Idea I tried to use the well-known store IKEA and its Catalog to implement my idea. This project aims <a class="read-more" href="https://mobile.fhstp.ac.at/development/webdevelopment/print2mobile-ar-web-application-to-project-3d-objects-onto-a-catalog/">[...]</a></p>
<p>The post <a href="https://mobile.fhstp.ac.at/development/webdevelopment/print2mobile-ar-web-application-to-project-3d-objects-onto-a-catalog/">Print2Mobile | AR &#8211; Web Application to project 3D Objects onto a Catalog</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Have you ever seen a cool piece of furniture in a furniture store catalog? Maybe you wondered how the couch looks from behind, or how to view the table in 3D to better estimate its dimensions.</p>



<p class="has-medium-font-size wp-block-paragraph"><strong>Idea</strong></p>



<p class="wp-block-paragraph">I tried to use the well-known store IKEA and its Catalog to implement my idea. This project aims to bridge the gap between physical catalogs and digital visualization. The core concept involves using QR codes placed on the product pages to link to an external web application. Once the user scans the code, this application can display a 3D model of the product above its corresponding picture in the physical catalog.</p>



<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-5 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-full is-resized is-style-default"><img loading="lazy" decoding="async" width="4080" height="3072" data-id="14937" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/PXL_20251016_083759829-3.jpg" alt="" class="wp-image-14937" style="width:519px;height:auto" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/PXL_20251016_083759829-3.jpg 4080w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/PXL_20251016_083759829-3-1536x1157.jpg 1536w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/PXL_20251016_083759829-3-2048x1542.jpg 2048w" sizes="auto, (max-width: 4080px) 100vw, 4080px" /></figure>



<figure class="wp-block-image size-full is-resized is-style-default"><img loading="lazy" decoding="async" width="960" height="2142" data-id="14932" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103642.jpg" alt="" class="wp-image-14932" style="width:221px;height:auto" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103642.jpg 960w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103642-688x1536.jpg 688w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103642-918x2048.jpg 918w" sizes="auto, (max-width: 960px) 100vw, 960px" /></figure>
</figure>



<p class="has-medium-font-size wp-block-paragraph"><strong>Implementation</strong></p>



<p class="wp-block-paragraph">For technologies, I used HTML, CSS and JavaScript. For the purpose of pattern recognition and tracking, I used the framework <a href="https://hiukim.github.io/mind-ar-js-doc/">mindAR.js</a>. For displaying the 3D Objects, I used the framework <a href="https://aframe.io/">A-Frame</a>. Sadly, the 3D objects that I used are very high fidelity and that impacts the performance of the application. So when you want to scan the furniture elements on the page, you have to be very careful, not to move too much or too quickly. It&#8217;s hosted on my own server as a docker instance using a Nginx web server.</p>



<figure class="wp-block-gallery has-nested-images columns-3 is-cropped wp-block-gallery-6 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-full is-resized"><img decoding="async" data-id="14910" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103732.jpg" alt="" class="wp-image-14910" style="width:306px;height:auto"/></figure>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="960" height="2142" data-id="14928" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103711-1.png" alt="" class="wp-image-14928" style="width:305px;height:auto" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103711-1.png 960w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103711-1-688x1536.png 688w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103711-1-918x2048.png 918w" sizes="auto, (max-width: 960px) 100vw, 960px" /></figure>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="960" height="2142" data-id="14929" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103739.jpg" alt="" class="wp-image-14929" style="width:321px;height:auto" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103739.jpg 960w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103739-688x1536.jpg 688w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103739-918x2048.jpg 918w" sizes="auto, (max-width: 960px) 100vw, 960px" /></figure>
</figure>



<p class="wp-block-paragraph">Link to the Webapplication: <a href="https://ikea-scanner.laschober.eu/">https://.ikea-scanner.laschober.eu</a></p>



<p class="wp-block-paragraph"><strong>Disclaimer:</strong> This project is <strong>not affiliated with, endorsed by, or in any way officially connected with IKEA</strong>. The use of IKEA&#8217;s name and catalog images is solely for illustrative purposes as a practical example for the proof of concept.</p>



<p class="wp-block-paragraph"></p>
<p>The post <a href="https://mobile.fhstp.ac.at/development/webdevelopment/print2mobile-ar-web-application-to-project-3d-objects-onto-a-catalog/">Print2Mobile | AR &#8211; Web Application to project 3D Objects onto a Catalog</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Print2Mobile &#124; Dishcovery</title>
		<link>https://mobile.fhstp.ac.at/studium/print2mobile-dishcovery/</link>
		
		<dc:creator><![CDATA[Kevin Kraushofer]]></dc:creator>
		<pubDate>Thu, 16 Oct 2025 10:31:34 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Projekte]]></category>
		<category><![CDATA[Studium]]></category>
		<category><![CDATA[Webdevelopment]]></category>
		<guid isPermaLink="false">https://mobile.fhstp.ac.at/?p=14879</guid>

					<description><![CDATA[<p>(QR Codes in the pictures have no functionality) We’ve all been there, standing in front of the fridge or in the supermarket aisle not knowing what to cook. Meanwhile, tons of perfectly good food end up in the trash every day. Dishcovery aims to change that.With a simple scan at your local supermarket or your <a class="read-more" href="https://mobile.fhstp.ac.at/studium/print2mobile-dishcovery/">[...]</a></p>
<p>The post <a href="https://mobile.fhstp.ac.at/studium/print2mobile-dishcovery/">Print2Mobile | Dishcovery</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph" style="font-size:9px">(QR Codes in the pictures have no functionality)</p>



<p class="wp-block-paragraph">We’ve all been there, standing in front of the fridge or in the supermarket aisle not knowing what to cook. Meanwhile, tons of perfectly good food end up in the trash every day.</p>



<div class="wp-block-group is-nowrap is-layout-flex wp-container-core-group-is-layout-8f761849 wp-block-group-is-layout-flex">
<p class="wp-block-paragraph"><strong>Dishcovery</strong> aims to change that.<br>With a simple scan at your local supermarket or your advertising in the letterbox, the app instantly shows you recipes that match your preferences <em>and</em> the store’s current stock including products nearing their expiry date. But that’s not all: you can also add ingredients you already have at home, and Dishcovery will automatically include them in the recipe suggestions. Every recipe comes with clear quantities, can be saved to your favorites and added directly to a digital shopping list. The app also shows how many products you’ve helped rescue, turning sustainability into something measurable and rewarding.</p>
</div>



<p class="wp-block-paragraph"></p>



<figure class="wp-block-gallery has-nested-images columns-4 is-cropped wp-block-gallery-7 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="393" height="852" data-id="14898" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Homepage-1.png" alt="figma prototype Homepage recipe" class="wp-image-14898"/></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="393" height="852" data-id="14899" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/homepage2.png" alt="figma prototype Homepage ingredients" class="wp-image-14899"/></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="393" height="852" data-id="14897" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Filter.png" alt="figma prototype filterpage overview" class="wp-image-14897"/></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="393" height="852" data-id="14896" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Filter2.png" alt="figma prototype filterpage whats in your kitchen page." class="wp-image-14896"/></figure>
</figure>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">Dishcovery not only inspires creativity in the kitchen — it makes sustainability simple, digital, and rewarding.</p>



<p class="wp-block-paragraph"></p>


<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="840" height="1188" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/website-mit-fake-qr-code-1-1.jpg" alt="" class="wp-image-14919" style="width:376px;height:auto"/></figure>
</div>


<p class="wp-block-paragraph"><strong>This project is not affiliated with BILLA or the REWE Group in any way. It is a purely academic student project, created for educational purposes only. No financial support, sponsorship, or compensation has been received from BILLA or any other company. Any use of names or logos is solely for demonstration purposes within the prototype.</strong></p>



<p class="wp-block-paragraph">iPhone design by Luis Orea<br>Icons designed by Kryston Schwarze https://coolicons.cool/<br>Illustrations/Icons designed by Freepick www.freepik.com</p>



<h2 class="wp-block-heading">Other Projects</h2>



<p class="wp-block-paragraph"><a href="https://swiva.app">Swiva</a> is a mobile app for saving ideas, places, recipes and activities from TikTok, Instagram, Maps and the web, and actually turning them into plans.</p>
<p>The post <a href="https://mobile.fhstp.ac.at/studium/print2mobile-dishcovery/">Print2Mobile | Dishcovery</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Projekt &#124; Powda Club-Dashboard</title>
		<link>https://mobile.fhstp.ac.at/development/projekt-powda-club-dashboard/</link>
		
		<dc:creator><![CDATA[David Grünberger]]></dc:creator>
		<pubDate>Thu, 16 Oct 2025 01:25:15 +0000</pubDate>
				<category><![CDATA[Allgemein]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Dokumentation]]></category>
		<category><![CDATA[Projekte]]></category>
		<category><![CDATA[Software]]></category>
		<category><![CDATA[Studium]]></category>
		<category><![CDATA[User Experience]]></category>
		<category><![CDATA[Webdevelopment]]></category>
		<category><![CDATA[clerk]]></category>
		<category><![CDATA[convex]]></category>
		<category><![CDATA[Next.js]]></category>
		<category><![CDATA[Semesterprojekt]]></category>
		<category><![CDATA[shadcn]]></category>
		<category><![CDATA[stripe]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[webhooks]]></category>
		<guid isPermaLink="false">https://mobile.fhstp.ac.at/?p=14812</guid>

					<description><![CDATA[<p>Im Rahmen des Semesterprojekts im zweiten Semester der Masterklasse Mobile habe ich ein Club Dashboard entwickelt – eine Webanwendung, mit der Skiclubs ihre Ausfahrten verwalten und Teilnehmer:innen direkt online buchen und bezahlen lassen können.Ziel des Projekts war es, einen funktionsfähigen MVP (Minimum Viable Product) zu erstellen, der eine vollständige End-to-End-Integration für Zahlungen bietet – von <a class="read-more" href="https://mobile.fhstp.ac.at/development/projekt-powda-club-dashboard/">[...]</a></p>
<p>The post <a href="https://mobile.fhstp.ac.at/development/projekt-powda-club-dashboard/">Projekt | Powda Club-Dashboard</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Im Rahmen des Semesterprojekts im zweiten Semester der Masterklasse Mobile habe ich ein <strong>Club Dashboard</strong> entwickelt – eine Webanwendung, mit der Skiclubs ihre <strong>Ausfahrten verwalten</strong> und <strong>Teilnehmer:innen direkt online buchen und bezahlen</strong> lassen können.<br>Ziel des Projekts war es, einen <strong>funktionsfähigen MVP (Minimum Viable Product)</strong> zu erstellen, der eine vollständige End-to-End-Integration für Zahlungen bietet – von der Erstellung der Events bis zur erfolgreichen Bezahlung über die Website.</p>



<p class="wp-block-paragraph">In diesem Beitrag möchte ich den Aufbau der Anwendung, die verwendeten Technologien sowie einige Herausforderungen und Learnings während der Umsetzung vorstellen.</p>



<h2 class="wp-block-heading">Technologie-Stack</h2>



<p class="wp-block-paragraph">Das Club Dashboard wurde als moderne Webanwendung mit Fokus auf Skalierbarkeit, Sicherheit und einfache Erweiterbarkeit entwickelt.<br>Hier ein Überblick über die zentralen Technologien:</p>



<h3 class="wp-block-heading">Framework: Next.js (App Router)</h3>



<p class="wp-block-paragraph">Für das Frontend kam <strong>Next.js</strong> mit dem neuen <strong>App Router</strong> zum Einsatz. Diese Architektur erlaubt serverseitiges Rendering (SSR), API-Routen und eine saubere Trennung von Client- und Server-Komponenten. Dadurch konnte ich sowohl UI-Logik als auch Backend-Funktionalität innerhalb eines Frameworks umsetzen.</p>



<p class="wp-block-paragraph"><strong>Vorteile:</strong></p>



<ul class="wp-block-list">
<li>Serverseitiges Rendering für bessere Performance und SEO</li>



<li>Integrierte API-Routen für serverseitige Logik (z. B. Payment Handling)</li>



<li>Moderne Struktur durch den App Router</li>
</ul>



<h3 class="wp-block-heading">Data Layer: Convex</h3>



<p class="wp-block-paragraph">Anstatt einer klassischen REST- oder GraphQL-API habe ich mich für <strong>Convex</strong> entschieden – eine innovative Backend-as-a-Service-Lösung, die reaktive Datenabfragen, serverseitige Funktionen und Auth-Integration out of the box bietet.</p>



<p class="wp-block-paragraph"><strong>Convex</strong> ermöglicht es, Daten- und Serverlogik in TypeScript zu schreiben und nahtlos mit dem Frontend zu verbinden. Dadurch entfällt ein Großteil der sonst notwendigen API- und Datenbankkonfiguration.</p>



<h3 class="wp-block-heading">Styling &amp; UI: TailwindCSS &amp; shadcn</h3>



<p class="wp-block-paragraph">Für das Styling habe ich auf <strong>TailwindCSS</strong> gesetzt – ein Utility-First-CSS-Framework, das schnelles Prototyping und einheitliches Design ermöglicht.<br>Zusätzlich kam <strong>shadcn</strong> zum Einsatz, eine leichtgewichtige Component Library mit Fokus auf Barrierefreiheit, Dark Mode und Customizability.</p>



<p class="wp-block-paragraph"><strong>Vorteile dieser Kombination:</strong></p>



<ul class="wp-block-list">
<li>Konsistentes UI mit klarer Design-Sprache</li>



<li>Wiederverwendbare Komponenten</li>



<li>Effiziente Entwicklung durch Utility-Klassen</li>



<li>Code Ownership: Components werden einfach ins Projekt kopiert</li>
</ul>



<h3 class="wp-block-heading">Authentifizierung: Clerk</h3>



<p class="wp-block-paragraph">Die Authentifizierung wird durch <strong>Clerk</strong> umgesetzt – denselben Anbieter, den ich bereits im ersten Semesterprojekt genutzt habe. Clerk bietet ein modernes User Management inklusive OAuth-Integration und JWT-Handling, was eine einfache Verbindung mit Convex und Next.js ermöglicht.</p>



<p class="wp-block-paragraph">Durch Clerk konnte ich schnell eine sichere Login-, Logout- und Session-Logik aufsetzen, ohne selbst komplexe Authentifizierungssysteme implementieren zu müssen.</p>



<h3 class="wp-block-heading">Payment Provider: Stripe</h3>



<p class="wp-block-paragraph">Ein zentrales Feature dieses Projekts war die <strong>Integration von Stripe</strong> als Payment Provider. Nutzer:innen können direkt über die Website eine Skiausfahrt buchen und bezahlen.<br>Dazu wurde das Stripe Checkout-System in die App eingebunden – inklusive <strong>Webhook-Handling</strong>, um Payment-Status-Änderungen serverseitig zu verarbeiten.</p>



<p class="wp-block-paragraph"><strong>Beispielhafte Flows:</strong></p>



<ul class="wp-block-list">
<li>User wählt eine Ausfahrt aus → Klick auf „Buchen“</li>



<li>Stripe Checkout öffnet sich → Zahlung erfolgt</li>



<li>Stripe sendet einen Webhook an das System → Buchung wird bestätigt und gespeichert</li>
</ul>



<p class="wp-block-paragraph">Diese End-to-End-Integration war der Kern des Projekts und erforderte ein gutes Zusammenspiel zwischen <strong>Frontend, Convex Backend und Stripe Webhooks</strong>.</p>



<h2 class="wp-block-heading">Kommunikation &amp; Webhooks</h2>



<p class="wp-block-paragraph">Ein spannender Teil des Projekts war die <strong>Kommunikation zwischen den verschiedenen Systemen</strong>.<br>Sowohl Stripe als auch Clerk arbeiten mit <strong>Webhooks</strong>, um Echtzeit-Informationen an das System zu senden (z. B. erfolgreiche Zahlungen oder neue Benutzer).<br>Die Webhooks werden über API-Routen in Next.js empfangen und anschließend in Convex weiterverarbeitet – beispielsweise um Buchungsdaten zu aktualisieren oder Rechnungen zu speichern.</p>



<h2 class="wp-block-heading">Herausforderungen &amp; Learnings</h2>



<p class="wp-block-paragraph">Während der Entwicklung gab es mehrere interessante Herausforderungen:</p>



<ul class="wp-block-list">
<li><strong>Webhook-Handling:</strong> Das Zusammenspiel von Stripe, Clerk und Convex erfordert saubere Trennung von Sicherheits- und Logikschichten. Insbesondere das Validieren von Webhook-Signaturen war anfänglich fehleranfällig.</li>



<li><strong>Datenmodellierung in Convex:</strong> Da Convex ein eigenes Datenmodell nutzt, war es notwendig, sich in dessen Schema-System einzuarbeiten.</li>



<li><strong>SSR + Client-Komponenten:</strong> Der Wechsel zwischen Server- und Client-Komponenten im Next.js App Router war zunächst ungewohnt und erforderte ein gutes Verständnis des Rendering-Modells.</li>
</ul>



<p class="wp-block-paragraph">Trotz dieser Hürden war die Entwicklung äußerst lehrreich – vor allem im Hinblick auf <strong>Payment-Flows, Security und API-Kommunikation</strong>.</p>



<h2 class="wp-block-heading">Fazit</h2>



<p class="wp-block-paragraph">Das Club Dashboard war ein spannendes Projekt, das mir erstmals die Umsetzung einer <strong>vollständigen End-to-End-Integration mit Stripe</strong> ermöglicht hat.<br>Durch den Einsatz moderner Tools wie <strong>Next.js, Convex, Clerk und Stripe</strong> konnte ich einen funktionalen MVP umsetzen, der reale Buchungsprozesse abbildet.</p>



<p class="wp-block-paragraph">Besonders interessant war zu sehen, wie gut sich die verschiedenen Services über <strong>Webhooks und API-Routen</strong> integrieren lassen – und wie wichtig saubere Datenflüsse und Sicherheit in einem Payment-System sind.</p>



<p class="wp-block-paragraph">Im nächsten Schritt plane ich, das Dashboard um weitere Funktionalitäten zu erweitern, um einem produktionsreifen Produkt näher zu kommen, das sich veröffentlichen und vertreiben lässt.</p>



<h2 class="wp-block-heading">Screenshots</h2>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1996" height="1594" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/landing_page.jpg" alt="" class="wp-image-14844" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/landing_page.jpg 1996w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/landing_page-1536x1227.jpg 1536w" sizes="auto, (max-width: 1996px) 100vw, 1996px" /><figcaption class="wp-element-caption">Screenshot: Landing Page</figcaption></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1993" height="1591" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/ausfahrten.png" alt="" class="wp-image-14845" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/ausfahrten.png 1993w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/ausfahrten-1536x1226.png 1536w" sizes="auto, (max-width: 1993px) 100vw, 1993px" /><figcaption class="wp-element-caption">Screenshot: Ausfahrtenliste mit Buchungsoption</figcaption></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2011" height="1600" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/dashboard.png" alt="" class="wp-image-14847" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/dashboard.png 2011w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/dashboard-1536x1222.png 1536w" sizes="auto, (max-width: 2011px) 100vw, 2011px" /><figcaption class="wp-element-caption">Screenshot: Ausfahrtenverwaltung am Dashboard</figcaption></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1990" height="1429" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/zahlungsinfos.png" alt="" class="wp-image-14848" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/zahlungsinfos.png 1990w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/zahlungsinfos-1536x1103.png 1536w" sizes="auto, (max-width: 1990px) 100vw, 1990px" /><figcaption class="wp-element-caption">Screenshot: Eingebettete Stripe Account Komponenten am Dashboard</figcaption></figure>



<p class="wp-block-paragraph"></p>
<p>The post <a href="https://mobile.fhstp.ac.at/development/projekt-powda-club-dashboard/">Projekt | Powda Club-Dashboard</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
