<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Obed Avorlenu</title>
    <description>The latest articles on DEV Community by Obed Avorlenu (@obed_avorlenu).</description>
    <link>https://dev.to/obed_avorlenu</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4008779%2F81cbce3e-3425-45ff-8fe7-f00ad9d74de3.png</url>
      <title>DEV Community: Obed Avorlenu</title>
      <link>https://dev.to/obed_avorlenu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/obed_avorlenu"/>
    <language>en</language>
    <item>
      <title>Building AfriWidget: How I Built 100+ Free Online Tools Using HTML, CSS and JavaScript</title>
      <dc:creator>Obed Avorlenu</dc:creator>
      <pubDate>Fri, 30 Oct 2026 01:04:23 +0000</pubDate>
      <link>https://dev.to/obed_avorlenu/building-afriwidget-how-i-built-100-free-online-tools-using-html-css-and-javascript-24i7</link>
      <guid>https://dev.to/obed_avorlenu/building-afriwidget-how-i-built-100-free-online-tools-using-html-css-and-javascript-24i7</guid>
      <description>&lt;p&gt;From an idea to a growing collection of practical web applications. A look at the development process, technical decisions, challenges and lessons behind AfriWidget.&lt;/p&gt;

&lt;p&gt;Building software is interesting. Building software that people can actually use to solve everyday problems is even more interesting.&lt;/p&gt;

&lt;p&gt;When I started working on AfriWidget, I wasn't trying to create another website filled with random online calculators. I wanted to build something more useful: a collection of practical digital tools that could make everyday tasks easier, particularly for people who often struggle to find tools designed around their actual needs.&lt;/p&gt;

&lt;p&gt;What began as an idea gradually developed into a growing platform containing more than 100 tools and pages across several categories.&lt;/p&gt;

&lt;p&gt;The interesting part is how it was built.&lt;/p&gt;

&lt;p&gt;AfriWidget was developed using familiar web technologies: HTML, CSS and JavaScript. There was no need to begin with a complicated technology stack or introduce a large collection of frameworks.&lt;/p&gt;

&lt;p&gt;Instead, the focus was on building individual tools, making them functional, improving their usability and gradually developing a platform around them.&lt;/p&gt;

&lt;p&gt;In this article, I'll explain the thinking behind AfriWidget, the development approach, the technologies involved, some of the engineering challenges and the lessons I learned while building it.&lt;/p&gt;

&lt;p&gt;This isn't a claim that everything was perfect. Like most real-world software projects, AfriWidget has required continuous improvements, corrections and difficult decisions.&lt;/p&gt;

&lt;p&gt;That's precisely what makes the development process worth discussing.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Idea: Building Tools That Solve Actual Problems&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every useful software product starts with a problem worth solving.&lt;/p&gt;

&lt;p&gt;People regularly need to calculate percentages, understand academic results, manage information, estimate expenses and complete other everyday tasks.&lt;/p&gt;

&lt;p&gt;Although many websites provide calculators and similar utilities, finding a tool that properly addresses a particular country's educational system, grading structure or specific requirements can sometimes be difficult.&lt;/p&gt;

&lt;p&gt;Consider academic calculations.&lt;/p&gt;

&lt;p&gt;A student might need to understand their examination aggregate, calculate a weighted assessment score or estimate whether their results meet a particular admission requirement.&lt;/p&gt;

&lt;p&gt;A generic calculator cannot necessarily handle these situations correctly.&lt;/p&gt;

&lt;p&gt;The calculation might depend on a specific grading structure, subject combination, weighting system or institutional requirement.&lt;/p&gt;

&lt;p&gt;This presented an interesting development opportunity.&lt;/p&gt;

&lt;p&gt;Instead of creating only general-purpose calculators, why not build tools that address specific problems?&lt;/p&gt;

&lt;p&gt;That became one of the ideas behind AfriWidget.&lt;/p&gt;

&lt;p&gt;The objective was to create practical tools that people could access directly through a browser without having to install an application.&lt;/p&gt;

&lt;p&gt;The platform would focus on usability, accessibility and solving clearly defined problems.&lt;/p&gt;

&lt;p&gt;The guiding idea became:&lt;/p&gt;

&lt;p&gt;Built in Ghana. Made for the World.&lt;/p&gt;

&lt;p&gt;AfriWidget isn't intended exclusively for African users. Its educational tools include region-specific functionality where necessary, while its broader collection addresses problems that people around the world experience.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Choosing the Technology Stack&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One of my earliest development decisions was choosing the technologies that would power the platform.&lt;/p&gt;

&lt;p&gt;I wanted a stack that would allow me to build quickly, maintain control over the implementation and avoid unnecessary complexity.&lt;/p&gt;

&lt;p&gt;The core technologies were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTML&lt;/li&gt;
&lt;li&gt;CSS&lt;/li&gt;
&lt;li&gt;JavaScript&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These three technologies provide everything needed to build interactive browser-based applications.&lt;/p&gt;

&lt;p&gt;HTML: Structuring the tools&lt;/p&gt;

&lt;p&gt;HTML provides the structure of each application.&lt;/p&gt;

&lt;p&gt;For a calculator, this includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input fields.&lt;/li&gt;
&lt;li&gt;Labels.&lt;/li&gt;
&lt;li&gt;Buttons.&lt;/li&gt;
&lt;li&gt;Result sections.&lt;/li&gt;
&lt;li&gt;Instructions.&lt;/li&gt;
&lt;li&gt;Validation messages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, a simple percentage calculator might require two numerical inputs and a button to calculate the result.&lt;/p&gt;

&lt;p&gt;HTML defines these elements and their relationships.&lt;/p&gt;

&lt;p&gt;I also wanted the pages to use meaningful semantic elements rather than relying entirely on generic containers.&lt;/p&gt;

&lt;p&gt;This helps make the content easier to understand for browsers, assistive technologies and search engines.&lt;/p&gt;

&lt;p&gt;CSS: Designing the experience&lt;/p&gt;

&lt;p&gt;CSS controls how the applications look and behave visually.&lt;/p&gt;

&lt;p&gt;For AfriWidget, the design direction uses a relatively simple visual identity built around black, yellow and white.&lt;/p&gt;

&lt;p&gt;The intention was to avoid unnecessary visual complexity.&lt;/p&gt;

&lt;p&gt;A calculator should make its purpose obvious.&lt;/p&gt;

&lt;p&gt;Users should understand where to enter information, how to perform a calculation and where to find the result.&lt;/p&gt;

&lt;p&gt;CSS also handles responsiveness.&lt;/p&gt;

&lt;p&gt;This is particularly important because many people access online tools using smartphones rather than desktop computers.&lt;/p&gt;

&lt;p&gt;The interface needs to remain usable on smaller screens without requiring users to zoom in or navigate unnecessarily complicated layouts.&lt;/p&gt;

&lt;p&gt;JavaScript: Making everything functional&lt;/p&gt;

&lt;p&gt;JavaScript handles the actual behaviour of the tools.&lt;/p&gt;

&lt;p&gt;It processes inputs, performs calculations, validates information and updates the interface.&lt;/p&gt;

&lt;p&gt;For example, consider a weighted academic score calculator.&lt;/p&gt;

&lt;p&gt;The calculation might involve several assessment components, each contributing a different percentage to the final result.&lt;/p&gt;

&lt;p&gt;JavaScript can receive the scores, apply the appropriate weights and calculate the final value.&lt;/p&gt;

&lt;p&gt;A simplified example:&lt;/p&gt;

&lt;p&gt;function calculateWeightedScore(&lt;br&gt;
    assignment,&lt;br&gt;
    test,&lt;br&gt;
    examination&lt;br&gt;
) {&lt;br&gt;
    const result =&lt;br&gt;
        (assignment * 0.20) +&lt;br&gt;
        (test * 0.30) +&lt;br&gt;
        (examination * 0.50);&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return result;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;console.log(&lt;br&gt;
    calculateWeightedScore(80, 70, 60)&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;The output is:&lt;/p&gt;

&lt;p&gt;67&lt;/p&gt;

&lt;p&gt;This is a small example of the calculation logic.&lt;/p&gt;

&lt;p&gt;A real application also needs input validation, appropriate score limits, error handling and clear result presentation.&lt;/p&gt;

&lt;p&gt;The important architectural decision was that the core functionality could be implemented directly in the browser without requiring a backend for every calculation.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Building Individual Tools Instead of One Complicated Application&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One of the most important decisions was treating the tools as individual applications within a larger platform.&lt;/p&gt;

&lt;p&gt;This approach makes it possible to develop different tools around their specific requirements.&lt;/p&gt;

&lt;p&gt;An academic calculator doesn't necessarily need the same input structure as a financial calculator.&lt;/p&gt;

&lt;p&gt;A productivity tool may require a completely different interaction model.&lt;/p&gt;

&lt;p&gt;Trying to force every tool into one complicated interface would introduce unnecessary limitations.&lt;/p&gt;

&lt;p&gt;Instead, each tool can have its own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Interface.&lt;/li&gt;
&lt;li&gt;Calculation logic.&lt;/li&gt;
&lt;li&gt;Validation rules.&lt;/li&gt;
&lt;li&gt;Instructions.&lt;/li&gt;
&lt;li&gt;Results presentation.&lt;/li&gt;
&lt;li&gt;Supporting content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This provides flexibility.&lt;/p&gt;

&lt;p&gt;It also means that improvements can be made to individual tools without automatically changing the behaviour of unrelated applications.&lt;/p&gt;

&lt;p&gt;However, this approach introduces another challenge: consistency.&lt;/p&gt;

&lt;p&gt;When a platform contains dozens or hundreds of individual tools, maintaining a consistent user experience becomes increasingly important.&lt;/p&gt;

&lt;p&gt;Users shouldn't feel like they are visiting completely unrelated websites whenever they move between tools.&lt;/p&gt;

&lt;p&gt;This is where shared design principles, navigation patterns and common functionality become useful.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Developing the Educational Tools&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Education became one of the central areas of AfriWidget.&lt;/p&gt;

&lt;p&gt;This wasn't simply about creating generic calculators.&lt;/p&gt;

&lt;p&gt;The challenge was understanding that different educational systems use different rules.&lt;/p&gt;

&lt;p&gt;A calculation that works for one country might be completely incorrect for another.&lt;/p&gt;

&lt;p&gt;WASSCE Aggregate Calculator&lt;/p&gt;

&lt;p&gt;The WASSCE calculator was designed around the grading requirements relevant to the examination system.&lt;/p&gt;

&lt;p&gt;The implementation needs to account for subject grades, the best-six calculation and relevant core subjects.&lt;/p&gt;

&lt;p&gt;This introduces several considerations.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which subjects are mandatory?&lt;/li&gt;
&lt;li&gt;Which subjects should contribute to the aggregate?&lt;/li&gt;
&lt;li&gt;How should grades be converted into numerical values?&lt;/li&gt;
&lt;li&gt;How should missing or invalid selections be handled?&lt;/li&gt;
&lt;li&gt;How should the result be explained?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The calculation logic must reflect the applicable grading rules.&lt;/p&gt;

&lt;p&gt;A visually attractive calculator is not useful if its underlying academic calculation is wrong.&lt;/p&gt;

&lt;p&gt;JAMB UTME Calculator&lt;/p&gt;

&lt;p&gt;The JAMB calculator requires a different calculation structure.&lt;/p&gt;

&lt;p&gt;Its inputs and scoring logic are not interchangeable with those of WASSCE.&lt;/p&gt;

&lt;p&gt;This illustrates an important software development principle:&lt;/p&gt;

&lt;p&gt;Similar-looking tools can have completely different business logic.&lt;/p&gt;

&lt;p&gt;The interface might share design patterns, but the underlying implementation must respect the requirements of the specific examination system.&lt;/p&gt;

&lt;p&gt;South African Educational Tools&lt;/p&gt;

&lt;p&gt;Another interesting area was developing tools for South African students.&lt;/p&gt;

&lt;p&gt;This included tools related to APS calculations and TVET assessment systems.&lt;/p&gt;

&lt;p&gt;The ICASS calculator was particularly interesting because it deals with continuous assessment components and their contribution to academic performance.&lt;/p&gt;

&lt;p&gt;For example, an assessment might contain different components with different weightings.&lt;/p&gt;

&lt;p&gt;The application must interpret those weights correctly before producing a result.&lt;/p&gt;

&lt;p&gt;Developing these tools reinforced an important lesson: understanding the domain is just as important as understanding JavaScript.&lt;/p&gt;

&lt;p&gt;You cannot reliably implement a specialised educational calculator simply by guessing the formula.&lt;/p&gt;

&lt;p&gt;You need to understand the actual requirements, verify the calculation and communicate any relevant limitations to users.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Making the Platform Useful Beyond Calculators&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As the project developed, it became clear that simply providing calculations was not enough.&lt;/p&gt;

&lt;p&gt;People often need help understanding their results and deciding what to do next.&lt;/p&gt;

&lt;p&gt;This led to the development of additional functionality.&lt;/p&gt;

&lt;p&gt;Find My Weaknesses&lt;/p&gt;

&lt;p&gt;One of the educational features focuses on helping students identify areas where they need improvement.&lt;/p&gt;

&lt;p&gt;The idea is to move beyond simply displaying a numerical result.&lt;/p&gt;

&lt;p&gt;A student might know their examination score but still struggle to understand which concepts require more attention.&lt;/p&gt;

&lt;p&gt;A weakness-identification experience can help direct attention towards specific topics and provide opportunities for practice.&lt;/p&gt;

&lt;p&gt;This introduces additional development requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Question organization.&lt;/li&gt;
&lt;li&gt;Difficulty levels.&lt;/li&gt;
&lt;li&gt;Answer validation.&lt;/li&gt;
&lt;li&gt;Performance tracking.&lt;/li&gt;
&lt;li&gt;Feedback.&lt;/li&gt;
&lt;li&gt;Progress monitoring.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also requires careful attention to the user experience.&lt;/p&gt;

&lt;p&gt;Students should not have to navigate a long, intimidating introduction before starting a useful activity.&lt;/p&gt;

&lt;p&gt;The experience should make it easy to begin.&lt;/p&gt;

&lt;p&gt;Continue Where You Left Off&lt;/p&gt;

&lt;p&gt;Another useful idea is reducing the amount of work users need to repeat.&lt;/p&gt;

&lt;p&gt;For example, someone using a calculator might leave the page and return later.&lt;/p&gt;

&lt;p&gt;A continuation feature can help users find previously used tools and, where supported, resume their work.&lt;/p&gt;

&lt;p&gt;This kind of functionality requires decisions about data persistence and privacy.&lt;/p&gt;

&lt;p&gt;If information is stored locally in the browser, users should have a clear way to remove it.&lt;/p&gt;

&lt;p&gt;Favourites&lt;/p&gt;

&lt;p&gt;A growing platform can make finding tools difficult.&lt;/p&gt;

&lt;p&gt;A favourites feature gives users a way to keep track of tools they use regularly.&lt;/p&gt;

&lt;p&gt;Rather than searching through multiple categories every time, they can access their preferred tools more easily.&lt;/p&gt;

&lt;p&gt;This is a relatively small feature, but it addresses a practical usability problem.&lt;/p&gt;

&lt;p&gt;Academic Progress Tracking&lt;/p&gt;

&lt;p&gt;Another direction is helping students understand their progress over time.&lt;/p&gt;

&lt;p&gt;A single examination result provides only a snapshot.&lt;/p&gt;

&lt;p&gt;Tracking multiple results can reveal whether performance is improving, declining or remaining relatively consistent.&lt;/p&gt;

&lt;p&gt;A useful progress tracker should present information clearly rather than overwhelming users with unnecessary statistics.&lt;/p&gt;

&lt;p&gt;These features represent a broader shift in the project's thinking.&lt;/p&gt;

&lt;p&gt;The objective is not simply to provide more calculators.&lt;/p&gt;

&lt;p&gt;It is to create more useful experiences around the problems those calculators address.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Why I Avoided an Unnecessarily Complicated Framework Stack&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Modern web development offers many frameworks and development environments.&lt;/p&gt;

&lt;p&gt;React, Vue, Angular, Next.js and other technologies provide powerful ways to build applications.&lt;/p&gt;

&lt;p&gt;They are valuable tools, and there are situations where using them makes perfect sense.&lt;/p&gt;

&lt;p&gt;However, not every project needs them.&lt;/p&gt;

&lt;p&gt;For AfriWidget, the initial priority was building lightweight browser-based tools.&lt;/p&gt;

&lt;p&gt;HTML, CSS and JavaScript were sufficient for implementing the core functionality.&lt;/p&gt;

&lt;p&gt;This offered several advantages.&lt;/p&gt;

&lt;p&gt;Less unnecessary complexity&lt;/p&gt;

&lt;p&gt;There was no requirement to introduce a large framework simply to calculate numbers, validate inputs and display results.&lt;/p&gt;

&lt;p&gt;Direct control&lt;/p&gt;

&lt;p&gt;The implementation remained close to the underlying browser technologies.&lt;/p&gt;

&lt;p&gt;Lightweight delivery&lt;/p&gt;

&lt;p&gt;Individual tools could be designed without requiring an unnecessarily large application runtime.&lt;/p&gt;

&lt;p&gt;Accessibility of development&lt;/p&gt;

&lt;p&gt;The core technologies are widely understood and have extensive documentation.&lt;/p&gt;

&lt;p&gt;There are trade-offs, of course.&lt;/p&gt;

&lt;p&gt;As the number of tools increases, maintaining consistency and avoiding duplicated code becomes more challenging.&lt;/p&gt;

&lt;p&gt;A collection of individually developed pages can gradually become difficult to manage if there are no clear conventions.&lt;/p&gt;

&lt;p&gt;This is an area where good organization, shared components where appropriate and consistent development practices become increasingly important.&lt;/p&gt;

&lt;p&gt;The lesson isn't that frameworks are bad.&lt;/p&gt;

&lt;p&gt;It's that technology choices should follow the needs of the project rather than fashion.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Importance of Input Validation and Calculation Accuracy&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One of the most important engineering lessons from building calculators is that accepting input is not the same as handling input correctly.&lt;/p&gt;

&lt;p&gt;Consider a calculator that expects a score between zero and one hundred.&lt;/p&gt;

&lt;p&gt;What happens if someone enters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A negative number?&lt;/li&gt;
&lt;li&gt;150?&lt;/li&gt;
&lt;li&gt;An empty field?&lt;/li&gt;
&lt;li&gt;Text instead of a number?&lt;/li&gt;
&lt;li&gt;A decimal value?&lt;/li&gt;
&lt;li&gt;An unusually large number?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without proper validation, the application might produce misleading results or behave unexpectedly.&lt;/p&gt;

&lt;p&gt;A simple JavaScript validation example looks like this:&lt;/p&gt;

&lt;p&gt;function validateScore(value) {&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const score = Number(value);

if (value === "" || !Number.isFinite(score)) {
    return {
        valid: false,
        message: "Please enter a valid number."
    };
}

if (score &amp;lt; 0 || score &amp;gt; 100) {
    return {
        valid: false,
        message: "Score must be between 0 and 100."
    };
}

return {
    valid: true,
    value: score
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;This function checks whether the input is a valid numerical value within the specified range.&lt;/p&gt;

&lt;p&gt;However, validation rules must be based on the actual requirements of each tool.&lt;/p&gt;

&lt;p&gt;Not every calculator should have the same limits.&lt;/p&gt;

&lt;p&gt;For example, a percentage calculator might accept values beyond 100 depending on its purpose, while a particular examination score field may have a strict maximum.&lt;/p&gt;

&lt;p&gt;Another important consideration is independent verification.&lt;/p&gt;

&lt;p&gt;For mathematical tools, I consider it essential to compare application results with manually calculated examples.&lt;/p&gt;

&lt;p&gt;A tool should not be considered complete simply because its button works.&lt;/p&gt;

&lt;p&gt;Its results must also be correct.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;SEO: Making Useful Tools Discoverable&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Building a useful application is only one part of creating a successful web platform.&lt;/p&gt;

&lt;p&gt;People also need to find it.&lt;/p&gt;

&lt;p&gt;Search engine optimization became an important part of developing AfriWidget.&lt;/p&gt;

&lt;p&gt;The challenge was that each tool addressed a different search intent.&lt;/p&gt;

&lt;p&gt;Someone searching for an ICASS calculator has a different requirement from someone searching for a WASSCE aggregate calculator.&lt;/p&gt;

&lt;p&gt;A single generic homepage cannot fully address every one of those needs.&lt;/p&gt;

&lt;p&gt;This made individual tool pages important.&lt;/p&gt;

&lt;p&gt;Each page needed to communicate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What the tool does.&lt;/li&gt;
&lt;li&gt;Who might benefit from it.&lt;/li&gt;
&lt;li&gt;What information users need.&lt;/li&gt;
&lt;li&gt;How the calculation works.&lt;/li&gt;
&lt;li&gt;How to interpret the result.&lt;/li&gt;
&lt;li&gt;What limitations apply.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Writing useful supporting content&lt;/p&gt;

&lt;p&gt;A calculator without sufficient explanation can leave users confused.&lt;/p&gt;

&lt;p&gt;For example, a student might receive an aggregate but not understand how it was calculated.&lt;/p&gt;

&lt;p&gt;Supporting content can explain the grading system, calculation method and relevant considerations.&lt;/p&gt;

&lt;p&gt;This provides value beyond the calculator itself.&lt;/p&gt;

&lt;p&gt;It also creates opportunities for search engines to understand the purpose of individual pages.&lt;/p&gt;

&lt;p&gt;However, publishing large quantities of repetitive content is not a sustainable SEO strategy.&lt;/p&gt;

&lt;p&gt;A page should exist because it serves a meaningful purpose.&lt;/p&gt;

&lt;p&gt;Adding paragraphs simply to increase word count does not automatically make a tool more useful.&lt;/p&gt;

&lt;p&gt;Technical SEO&lt;/p&gt;

&lt;p&gt;Technical considerations also matter.&lt;/p&gt;

&lt;p&gt;These include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Descriptive page titles.&lt;/li&gt;
&lt;li&gt;Useful meta descriptions.&lt;/li&gt;
&lt;li&gt;Logical heading structures.&lt;/li&gt;
&lt;li&gt;Canonical URLs.&lt;/li&gt;
&lt;li&gt;Internal links.&lt;/li&gt;
&lt;li&gt;Mobile usability.&lt;/li&gt;
&lt;li&gt;Search engine accessibility.&lt;/li&gt;
&lt;li&gt;Structured data where appropriate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Structured data can help search engines understand certain types of content, but it should accurately describe what users can actually see on the page.&lt;/p&gt;

&lt;p&gt;SEO should support the product rather than dictate misleading content.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Performance and the Importance of Lightweight Tools&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A website does not become successful simply because it contains many features.&lt;/p&gt;

&lt;p&gt;If users have to wait too long for a calculator to load, they may leave before using it.&lt;/p&gt;

&lt;p&gt;Performance was therefore an important consideration.&lt;/p&gt;

&lt;p&gt;Using browser-native technologies provides opportunities to keep individual tools relatively lightweight.&lt;/p&gt;

&lt;p&gt;However, writing vanilla JavaScript does not automatically guarantee good performance.&lt;/p&gt;

&lt;p&gt;Poorly optimized JavaScript can still create slow interfaces.&lt;/p&gt;

&lt;p&gt;Large images, unnecessary scripts, excessive DOM updates and inefficient event handling can affect the user experience.&lt;/p&gt;

&lt;p&gt;Some practical considerations include:&lt;/p&gt;

&lt;p&gt;Keep dependencies under control.&lt;/p&gt;

&lt;p&gt;Every external library should serve a meaningful purpose.&lt;/p&gt;

&lt;p&gt;Optimize images.&lt;/p&gt;

&lt;p&gt;Use appropriate image dimensions and modern formats where suitable.&lt;/p&gt;

&lt;p&gt;Avoid unnecessary JavaScript execution.&lt;/p&gt;

&lt;p&gt;Only perform work when it is needed.&lt;/p&gt;

&lt;p&gt;Keep interfaces responsive.&lt;/p&gt;

&lt;p&gt;A calculator should not become difficult to use when the screen becomes smaller.&lt;/p&gt;

&lt;p&gt;Test on real devices.&lt;/p&gt;

&lt;p&gt;Desktop testing alone does not reveal every mobile performance problem.&lt;/p&gt;

&lt;p&gt;A website should be evaluated under realistic conditions, including slower connections and less powerful devices.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Browser Storage and User Privacy&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As AfriWidget introduced more interactive features, browser storage became increasingly relevant.&lt;/p&gt;

&lt;p&gt;Some functionality is more convenient when information can be retained locally.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Favourite tools.&lt;/li&gt;
&lt;li&gt;Previously used tools.&lt;/li&gt;
&lt;li&gt;Saved calculation information.&lt;/li&gt;
&lt;li&gt;Academic progress.&lt;/li&gt;
&lt;li&gt;User preferences.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;JavaScript provides browser storage mechanisms such as localStorage.&lt;/p&gt;

&lt;p&gt;A simple example:&lt;/p&gt;

&lt;p&gt;const preferences = {&lt;br&gt;
    theme: "dark",&lt;br&gt;
    language: "en"&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;localStorage.setItem(&lt;br&gt;
    "userPreferences",&lt;br&gt;
    JSON.stringify(preferences)&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;The information can later be retrieved:&lt;/p&gt;

&lt;p&gt;const savedPreferences = JSON.parse(&lt;br&gt;
    localStorage.getItem("userPreferences") || "{}"&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;console.log(savedPreferences.theme);&lt;/p&gt;

&lt;p&gt;This allows an application to preserve certain preferences without requiring a server-side database.&lt;/p&gt;

&lt;p&gt;However, local storage introduces responsibilities.&lt;/p&gt;

&lt;p&gt;Developers should consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What information is being stored?&lt;/li&gt;
&lt;li&gt;Why does the application need it?&lt;/li&gt;
&lt;li&gt;How long should it remain?&lt;/li&gt;
&lt;li&gt;Can users delete it?&lt;/li&gt;
&lt;li&gt;Is sensitive information being stored unnecessarily?&lt;/li&gt;
&lt;li&gt;What happens when stored data becomes incompatible with a newer version?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Local storage should not be treated as a secure place for passwords, authentication secrets or highly sensitive personal information.&lt;/p&gt;

&lt;p&gt;Privacy also needs to be explained clearly.&lt;/p&gt;

&lt;p&gt;If an application stores user information locally, users should know what is stored and how to remove it.&lt;/p&gt;

&lt;p&gt;Convenience should not come at the expense of transparency.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Challenges That Come With Scaling a Collection of Tools&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One of the interesting things about AfriWidget is how quickly a small collection of tools can grow into a much larger platform.&lt;/p&gt;

&lt;p&gt;Developing five calculators is one thing.&lt;/p&gt;

&lt;p&gt;Maintaining more than one hundred tools and pages introduces an entirely different set of challenges.&lt;/p&gt;

&lt;p&gt;Maintaining consistency&lt;/p&gt;

&lt;p&gt;Different tools can gradually develop different design patterns.&lt;/p&gt;

&lt;p&gt;Buttons might have inconsistent sizes.&lt;/p&gt;

&lt;p&gt;Input fields might behave differently.&lt;/p&gt;

&lt;p&gt;Navigation might become difficult to predict.&lt;/p&gt;

&lt;p&gt;This creates a fragmented experience.&lt;/p&gt;

&lt;p&gt;The solution is to establish shared design principles and review individual tools regularly.&lt;/p&gt;

&lt;p&gt;Avoiding duplicated logic&lt;/p&gt;

&lt;p&gt;When several tools use similar functionality, developers may end up implementing the same logic repeatedly.&lt;/p&gt;

&lt;p&gt;For example, multiple calculators might need numerical validation.&lt;/p&gt;

&lt;p&gt;Without reusable patterns, maintaining those implementations can become unnecessarily difficult.&lt;/p&gt;

&lt;p&gt;A useful approach is identifying functionality that can be shared without forcing unrelated tools into the same implementation.&lt;/p&gt;

&lt;p&gt;Managing changes safely&lt;/p&gt;

&lt;p&gt;Updating a platform with many independent pages requires care.&lt;/p&gt;

&lt;p&gt;A change that improves one tool should not unexpectedly break another.&lt;/p&gt;

&lt;p&gt;This makes regression testing important.&lt;/p&gt;

&lt;p&gt;After making changes, developers should check both the modified functionality and important existing behaviour.&lt;/p&gt;

&lt;p&gt;Knowing when to remove features&lt;/p&gt;

&lt;p&gt;There is a common assumption that adding more features always improves a product.&lt;/p&gt;

&lt;p&gt;It doesn't.&lt;/p&gt;

&lt;p&gt;Sometimes a feature introduces maintenance costs without providing enough value.&lt;/p&gt;

&lt;p&gt;Sometimes an entire category needs reconsideration because the available tools do not meet the quality standards of the platform.&lt;/p&gt;

&lt;p&gt;Removing unnecessary functionality can be just as important as introducing new functionality.&lt;/p&gt;

&lt;p&gt;A healthy product should evolve based on usefulness, reliability and actual user needs.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hosting and Deployment&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A web application needs to be accessible beyond the developer's local environment.&lt;/p&gt;

&lt;p&gt;For a static-first platform like AfriWidget, static hosting is a practical approach.&lt;/p&gt;

&lt;p&gt;Services such as Cloudflare Pages can host static HTML, CSS and JavaScript projects and distribute content through a global network.&lt;/p&gt;

&lt;p&gt;The basic deployment process involves:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Preparing the project files.&lt;/li&gt;
&lt;li&gt;Organizing the pages and assets.&lt;/li&gt;
&lt;li&gt;Connecting the project to a supported deployment workflow.&lt;/li&gt;
&lt;li&gt;Configuring the domain.&lt;/li&gt;
&lt;li&gt;Deploying the website.&lt;/li&gt;
&lt;li&gt;Testing the published pages.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A major advantage of static hosting is that individual browser-based tools do not necessarily need a continuously running application server.&lt;/p&gt;

&lt;p&gt;The browser performs the calculations.&lt;/p&gt;

&lt;p&gt;The hosting platform delivers the files.&lt;/p&gt;

&lt;p&gt;This can reduce infrastructure complexity for tools that do not require server-side processing.&lt;/p&gt;

&lt;p&gt;However, not every future feature will necessarily fit this architecture.&lt;/p&gt;

&lt;p&gt;Features involving authentication, protected information, server-side AI processing or external integrations may require additional infrastructure.&lt;/p&gt;

&lt;p&gt;Choosing a static-first approach does not mean refusing to use backend services when they become necessary.&lt;/p&gt;

&lt;p&gt;It means avoiding infrastructure that the current problem does not require.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Learning From Real Usage Instead of Guessing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Building a website and understanding how people use it are two different things.&lt;/p&gt;

&lt;p&gt;Analytics can help developers discover what is happening after deployment.&lt;/p&gt;

&lt;p&gt;For example, a developer might assume that a newly introduced feature is popular because it took considerable time to build.&lt;/p&gt;

&lt;p&gt;Actual usage might tell a different story.&lt;/p&gt;

&lt;p&gt;Some tools may attract substantial search traffic.&lt;/p&gt;

&lt;p&gt;Others may receive very little attention.&lt;/p&gt;

&lt;p&gt;Some visitors may interact with a calculator and leave immediately because they obtained the answer they needed.&lt;/p&gt;

&lt;p&gt;That is not necessarily a negative experience.&lt;/p&gt;

&lt;p&gt;A short session can still represent a successful interaction.&lt;/p&gt;

&lt;p&gt;Analytics should therefore be interpreted in context.&lt;/p&gt;

&lt;p&gt;Useful measurements can include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Page views.&lt;/li&gt;
&lt;li&gt;Active users.&lt;/li&gt;
&lt;li&gt;Tool interactions.&lt;/li&gt;
&lt;li&gt;Search impressions.&lt;/li&gt;
&lt;li&gt;Search clicks.&lt;/li&gt;
&lt;li&gt;Search rankings.&lt;/li&gt;
&lt;li&gt;Conversion events.&lt;/li&gt;
&lt;li&gt;Errors and failed interactions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One important lesson is that traffic alone doesn't establish product quality.&lt;/p&gt;

&lt;p&gt;A tool that attracts thousands of visitors but regularly produces incorrect results is not a successful product.&lt;/p&gt;

&lt;p&gt;Likewise, a useful tool with limited visibility may need better discoverability rather than a complete redesign.&lt;/p&gt;

&lt;p&gt;The goal is to combine user feedback, technical testing and analytics when deciding what to improve.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What I Would Do Differently If I Started Again&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Looking back at the development process, there are several areas I would approach more deliberately.&lt;/p&gt;

&lt;p&gt;Establish stronger shared conventions earlier&lt;/p&gt;

&lt;p&gt;When building many independent tools, consistency becomes increasingly important.&lt;/p&gt;

&lt;p&gt;I would establish clearer conventions for file organization, naming, shared functionality and design patterns from the beginning.&lt;/p&gt;

&lt;p&gt;Build testing into the development process&lt;/p&gt;

&lt;p&gt;Testing should not become an activity reserved for the end of development.&lt;/p&gt;

&lt;p&gt;Each tool should have a defined set of expected inputs, outputs and edge cases.&lt;/p&gt;

&lt;p&gt;This is particularly important for academic and financial calculations.&lt;/p&gt;

&lt;p&gt;Prioritize quality over quantity&lt;/p&gt;

&lt;p&gt;It is tempting to measure progress by the number of tools published.&lt;/p&gt;

&lt;p&gt;However, a smaller collection of accurate, well-maintained tools can provide more value than a much larger collection of unfinished or poorly explained applications.&lt;/p&gt;

&lt;p&gt;Think about privacy before introducing storage&lt;/p&gt;

&lt;p&gt;Features such as favourites, saved progress and continuation functionality should be designed with privacy in mind.&lt;/p&gt;

&lt;p&gt;Users should understand what information is retained and how they can remove it.&lt;/p&gt;

&lt;p&gt;Treat content as part of the product&lt;/p&gt;

&lt;p&gt;Instructions, explanations and supporting articles are not merely SEO additions.&lt;/p&gt;

&lt;p&gt;They help users understand how a tool works and whether its results are relevant to their situation.&lt;/p&gt;

&lt;p&gt;This is particularly important when dealing with educational calculations.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What Comes Next for AfriWidget?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;AfriWidget is an evolving project.&lt;/p&gt;

&lt;p&gt;The intention is to continue improving the quality and usefulness of its existing tools rather than simply increasing the number of published pages.&lt;/p&gt;

&lt;p&gt;Some important areas of continued development include:&lt;/p&gt;

&lt;p&gt;Better educational experiences&lt;/p&gt;

&lt;p&gt;Making academic tools more useful through clearer explanations, performance tracking and opportunities for students to identify areas that need improvement.&lt;/p&gt;

&lt;p&gt;Improved user experience&lt;/p&gt;

&lt;p&gt;Reducing unnecessary steps, improving mobile usability and making important actions easier to understand.&lt;/p&gt;

&lt;p&gt;More reliable calculations&lt;/p&gt;

&lt;p&gt;Reviewing formulas, handling edge cases and providing transparent explanations of calculation methods.&lt;/p&gt;

&lt;p&gt;Better organization&lt;/p&gt;

&lt;p&gt;Making it easier for visitors to discover relevant tools without navigating unnecessarily complicated categories.&lt;/p&gt;

&lt;p&gt;Thoughtful new functionality&lt;/p&gt;

&lt;p&gt;Introducing features when they solve genuine problems rather than adding them simply because they are technically possible.&lt;/p&gt;

&lt;p&gt;The long-term objective is to make AfriWidget a useful collection of digital tools for people in different parts of the world.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Final Thoughts: Build Something Useful, Then Keep Improving It&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;AfriWidget has taught me that building a software product is not just about writing code.&lt;/p&gt;

&lt;p&gt;It involves identifying problems, understanding users, making technical decisions, testing assumptions and accepting that the first implementation will not always be the best one.&lt;/p&gt;

&lt;p&gt;HTML, CSS and JavaScript made it possible to build a growing collection of browser-based tools without introducing unnecessary complexity at the beginning.&lt;/p&gt;

&lt;p&gt;But the technologies themselves were never the main achievement.&lt;/p&gt;

&lt;p&gt;The more important part was identifying problems worth solving and continuously improving the solutions.&lt;/p&gt;

&lt;p&gt;There is a lesson here for developers, particularly those working independently.&lt;/p&gt;

&lt;p&gt;You don't need to begin with an enormous application.&lt;/p&gt;

&lt;p&gt;You don't need to use every popular framework.&lt;/p&gt;

&lt;p&gt;You don't need a large team before you can create something useful.&lt;/p&gt;

&lt;p&gt;Start with a problem you understand.&lt;/p&gt;

&lt;p&gt;Build a small solution.&lt;/p&gt;

&lt;p&gt;Test it properly.&lt;/p&gt;

&lt;p&gt;Listen to how people use it.&lt;/p&gt;

&lt;p&gt;Improve what doesn't work.&lt;/p&gt;

&lt;p&gt;And don't be afraid to remove something that no longer serves a purpose.&lt;/p&gt;

&lt;p&gt;A project becomes more valuable not simply because it grows, but because it becomes better at doing what it was created to do.&lt;/p&gt;

&lt;p&gt;That is the principle I want AfriWidget to continue following.&lt;/p&gt;




&lt;p&gt;Explore the Project&lt;/p&gt;

&lt;p&gt;AfriWidget is an independent project founded by Obed Avorlenu.&lt;/p&gt;

&lt;p&gt;It is built in Ghana and developed with the intention of making practical digital tools accessible to people around the world.&lt;/p&gt;

&lt;p&gt;The platform includes tools covering education, finance, productivity, business and other everyday needs.&lt;/p&gt;

&lt;p&gt;You can explore the project here:&lt;/p&gt;

&lt;p&gt;"AfriWidget — Built in Ghana. Made for the World." (&lt;a href="https://afriwidget.com" rel="noopener noreferrer"&gt;https://afriwidget.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;I'm also interested in hearing from other developers who have built independent products using HTML, CSS and JavaScript.&lt;/p&gt;

&lt;p&gt;Discussion questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What would you choose for a project like this: vanilla JavaScript or a framework?&lt;/li&gt;
&lt;li&gt;How do you maintain consistency when managing dozens or hundreds of independent tools?&lt;/li&gt;
&lt;li&gt;What is one technical decision you would make differently if you started your first major project again?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I'd be particularly interested in hearing from developers who have experience building and maintaining large collections of small web applications.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Build Your Own Smart AI Assistant Using Only Your Android Phone (Termux + Python)</title>
      <dc:creator>Obed Avorlenu</dc:creator>
      <pubDate>Fri, 30 Oct 2026 01:01:18 +0000</pubDate>
      <link>https://dev.to/obed_avorlenu/how-to-build-your-own-smart-ai-assistant-using-only-your-android-phone-termux-python-eog</link>
      <guid>https://dev.to/obed_avorlenu/how-to-build-your-own-smart-ai-assistant-using-only-your-android-phone-termux-python-eog</guid>
      <description>&lt;p&gt;How to Build Your Own Smart AI Assistant Using Only Your Android Phone (Termux + Python)&lt;/p&gt;

&lt;p&gt;Have you ever wanted to build your own AI assistant but assumed you needed an expensive computer, powerful graphics card or years of programming experience?&lt;/p&gt;

&lt;p&gt;What if I told you that you could build a working AI assistant using nothing more than your Android phone?&lt;/p&gt;

&lt;p&gt;You don't need a laptop.&lt;/p&gt;

&lt;p&gt;You don't need an expensive development environment.&lt;/p&gt;

&lt;p&gt;You don't even need to be an experienced programmer.&lt;/p&gt;

&lt;p&gt;With Termux, Python and an AI API, you can create a personal AI assistant directly from your Android device.&lt;/p&gt;

&lt;p&gt;In this tutorial, we're going to build one step by step.&lt;/p&gt;

&lt;p&gt;But we're not going to stop at creating a basic chatbot that simply accepts a question and returns an answer.&lt;/p&gt;

&lt;p&gt;We'll build an assistant that has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Its own personality and instructions.&lt;/li&gt;
&lt;li&gt;The ability to hold conversations.&lt;/li&gt;
&lt;li&gt;Short-term conversational memory.&lt;/li&gt;
&lt;li&gt;A customizable name.&lt;/li&gt;
&lt;li&gt;The ability to remember previous messages during a session.&lt;/li&gt;
&lt;li&gt;Commands to reset conversations and exit.&lt;/li&gt;
&lt;li&gt;Basic error handling.&lt;/li&gt;
&lt;li&gt;A simple terminal interface.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end, you'll understand how the different parts work and how to extend the project into something more advanced.&lt;/p&gt;

&lt;p&gt;You can follow this tutorial even if you've never built an AI application before.&lt;/p&gt;

&lt;p&gt;Let's get started.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First, Understand What We're Building&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before installing anything, let's understand how our assistant will work.&lt;/p&gt;

&lt;p&gt;An AI assistant generally has several components.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The interface&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is where you communicate with your assistant.&lt;/p&gt;

&lt;p&gt;In our project, the interface will be the Termux terminal.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The programming language&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We'll use Python to handle the application's logic.&lt;/p&gt;

&lt;p&gt;Python will receive your messages, organize conversation history and communicate with the AI service.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The AI model&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the component responsible for understanding questions and generating intelligent responses.&lt;/p&gt;

&lt;p&gt;Instead of training a massive AI model ourselves, we'll connect our application to an existing model through an API.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Memory&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We'll create a system that keeps previous messages during the conversation.&lt;/p&gt;

&lt;p&gt;This allows the assistant to understand follow-up questions.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;You: My name is Daniel.&lt;/p&gt;

&lt;p&gt;Assistant: Nice to meet you, Daniel!&lt;/p&gt;

&lt;p&gt;You: What is my name?&lt;/p&gt;

&lt;p&gt;Assistant: Your name is Daniel.&lt;/p&gt;

&lt;p&gt;Without conversation history, the assistant might not understand the second question.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Personality and instructions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We'll give our assistant specific instructions about how it should communicate, explain things and respond to users.&lt;/p&gt;

&lt;p&gt;This is what makes it behave more like a personal assistant rather than a generic chatbot.&lt;/p&gt;

&lt;p&gt;How everything connects&lt;/p&gt;

&lt;p&gt;Our application will follow this process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You enter a message.&lt;/li&gt;
&lt;li&gt;Python receives the message.&lt;/li&gt;
&lt;li&gt;Python adds it to the conversation history.&lt;/li&gt;
&lt;li&gt;The application sends the conversation to the AI model.&lt;/li&gt;
&lt;li&gt;The AI generates a response.&lt;/li&gt;
&lt;li&gt;Python displays the response.&lt;/li&gt;
&lt;li&gt;The conversation history is updated.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's the basic architecture.&lt;/p&gt;

&lt;p&gt;An important distinction: Termux and Python will run on your phone, but the AI model will run on a remote server. Therefore, this first version requires an internet connection.&lt;/p&gt;

&lt;p&gt;Your phone is building and controlling the assistant, not performing the expensive AI inference itself.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What You Need Before Starting&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You only need three main things.&lt;/p&gt;

&lt;p&gt;A. An Android phone&lt;/p&gt;

&lt;p&gt;Any reasonably modern Android device supported by the current Termux release should be suitable for this lightweight project.&lt;/p&gt;

&lt;p&gt;You don't need a flagship phone.&lt;/p&gt;

&lt;p&gt;Our application will not be running a massive language model locally.&lt;/p&gt;

&lt;p&gt;B. Termux&lt;/p&gt;

&lt;p&gt;Termux is an Android terminal emulator and Linux environment.&lt;/p&gt;

&lt;p&gt;It allows you to run programming languages, install development packages and execute commands directly from your phone.&lt;/p&gt;

&lt;p&gt;Download it from an official distribution linked by the Termux project.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Termux website: &lt;a href="https://termux.dev/" rel="noopener noreferrer"&gt;https://termux.dev/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Official GitHub repository: &lt;a href="https://github.com/termux/termux-app" rel="noopener noreferrer"&gt;https://github.com/termux/termux-app&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid mixing Termux installations from different distribution sources. Its companion applications and plugins should come from the same source.&lt;/p&gt;

&lt;p&gt;C. An AI API key&lt;/p&gt;

&lt;p&gt;We'll use an AI API to connect our Python application to an existing language model.&lt;/p&gt;

&lt;p&gt;An API allows one application to communicate with another application or service.&lt;/p&gt;

&lt;p&gt;For example, instead of building a language model capable of understanding thousands of different questions, we send the question to an existing AI model and receive its response.&lt;/p&gt;

&lt;p&gt;You can explore available models and API access through:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://platform.openai.com/" rel="noopener noreferrer"&gt;https://platform.openai.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You may need to create an account and configure API billing.&lt;/p&gt;

&lt;p&gt;Important: API usage may cost money. Check the current pricing and your account's available access before making requests.&lt;/p&gt;

&lt;p&gt;Also, an API key is a private credential. Never publish it in your code, GitHub repository or screenshots.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prepare Termux for Development&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Open Termux.&lt;/p&gt;

&lt;p&gt;You should see a terminal where you can enter commands.&lt;/p&gt;

&lt;p&gt;Don't worry if this looks unfamiliar. We'll explain what each command does.&lt;/p&gt;

&lt;p&gt;First, update the installed package information.&lt;/p&gt;

&lt;p&gt;Enter:&lt;/p&gt;

&lt;p&gt;pkg update &amp;amp;&amp;amp; pkg upgrade -y&lt;/p&gt;

&lt;p&gt;This checks for available package updates and installs them.&lt;/p&gt;

&lt;p&gt;Next, install Python:&lt;/p&gt;

&lt;p&gt;pkg install python -y&lt;/p&gt;

&lt;p&gt;Python is the programming language we'll use to build the assistant.&lt;/p&gt;

&lt;p&gt;Check whether the installation was successful:&lt;/p&gt;

&lt;p&gt;python --version&lt;/p&gt;

&lt;p&gt;You should see a Python version displayed.&lt;/p&gt;

&lt;p&gt;Now install Git:&lt;/p&gt;

&lt;p&gt;pkg install git -y&lt;/p&gt;

&lt;p&gt;Git is a version-control tool that helps developers manage changes to their projects.&lt;/p&gt;

&lt;p&gt;We won't need Git for the basic assistant, but it's useful to have when developing larger applications.&lt;/p&gt;

&lt;p&gt;Next, upgrade pip:&lt;/p&gt;

&lt;p&gt;python -m pip install --upgrade pip&lt;/p&gt;

&lt;p&gt;Pip is Python's package installer. It allows us to install additional libraries.&lt;/p&gt;

&lt;p&gt;Our development environment is now ready.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create Your AI Assistant Project&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's create a dedicated folder for the project.&lt;/p&gt;

&lt;p&gt;Enter:&lt;/p&gt;

&lt;p&gt;mkdir smart-ai&lt;/p&gt;

&lt;p&gt;This creates a directory called "smart-ai".&lt;/p&gt;

&lt;p&gt;Move into it:&lt;/p&gt;

&lt;p&gt;cd smart-ai&lt;/p&gt;

&lt;p&gt;Your terminal is now working inside the project directory.&lt;/p&gt;

&lt;p&gt;Let's create a Python virtual environment.&lt;/p&gt;

&lt;p&gt;python -m venv venv&lt;/p&gt;

&lt;p&gt;A virtual environment gives your project its own Python package installation space.&lt;/p&gt;

&lt;p&gt;Activate it:&lt;/p&gt;

&lt;p&gt;source venv/bin/activate&lt;/p&gt;

&lt;p&gt;If activation succeeds, you should see something similar to "(venv)" at the beginning of your terminal prompt.&lt;/p&gt;

&lt;p&gt;This helps keep project dependencies separate from other Python projects.&lt;/p&gt;

&lt;p&gt;Now install the official OpenAI Python library:&lt;/p&gt;

&lt;p&gt;python -m pip install openai&lt;/p&gt;

&lt;p&gt;Wait for the installation to finish.&lt;/p&gt;

&lt;p&gt;The library allows our Python application to communicate with the OpenAI API without manually constructing every HTTP request.&lt;/p&gt;

&lt;p&gt;We are now ready to write the actual assistant.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create Your AI API Key&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before writing the code, we need to configure access to the AI model.&lt;/p&gt;

&lt;p&gt;Visit:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://platform.openai.com/" rel="noopener noreferrer"&gt;https://platform.openai.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sign in and navigate to the API key section.&lt;/p&gt;

&lt;p&gt;Create a new secret API key.&lt;/p&gt;

&lt;p&gt;Copy it somewhere temporarily safe.&lt;/p&gt;

&lt;p&gt;Do not share it with anyone.&lt;/p&gt;

&lt;p&gt;Return to Termux.&lt;/p&gt;

&lt;p&gt;Enter:&lt;/p&gt;

&lt;p&gt;export OPENAI_API_KEY="YOUR_API_KEY_HERE"&lt;/p&gt;

&lt;p&gt;Replace "YOUR_API_KEY_HERE" with your actual API key.&lt;/p&gt;

&lt;p&gt;For example, the command should follow this structure:&lt;/p&gt;

&lt;p&gt;export OPENAI_API_KEY="sk-your-private-key"&lt;/p&gt;

&lt;p&gt;The example above is illustrative, not a working key.&lt;/p&gt;

&lt;p&gt;This command creates an environment variable that Python can access.&lt;/p&gt;

&lt;p&gt;Why not simply put the API key inside the Python script?&lt;/p&gt;

&lt;p&gt;Because separating credentials from application code reduces the chance of accidentally exposing them when sharing your project.&lt;/p&gt;

&lt;p&gt;Remember that environment variables set this way generally apply to the current shell session. You'll need to configure the key again when starting a new session unless you set up a secure alternative.&lt;/p&gt;

&lt;p&gt;Never upload your real key to GitHub.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the Main AI Assistant&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now comes the exciting part.&lt;/p&gt;

&lt;p&gt;We're going to create the actual application.&lt;/p&gt;

&lt;p&gt;Create a Python file:&lt;/p&gt;

&lt;p&gt;nano assistant.py&lt;/p&gt;

&lt;p&gt;Nano is a terminal text editor.&lt;/p&gt;

&lt;p&gt;Paste the following complete code into the editor.&lt;/p&gt;

&lt;p&gt;import os&lt;br&gt;
from openai import OpenAI&lt;/p&gt;

&lt;h1&gt;
  
  
  Check API credentials
&lt;/h1&gt;

&lt;p&gt;API_KEY = os.getenv("OPENAI_API_KEY")&lt;/p&gt;

&lt;p&gt;if not API_KEY:&lt;br&gt;
    print("Error: OPENAI_API_KEY is not configured.")&lt;br&gt;
    raise SystemExit(1)&lt;/p&gt;

&lt;h1&gt;
  
  
  Initialize AI client
&lt;/h1&gt;

&lt;p&gt;client = OpenAI(api_key=API_KEY)&lt;/p&gt;

&lt;h1&gt;
  
  
  Choose a model available to your account
&lt;/h1&gt;

&lt;p&gt;MODEL = os.getenv("OPENAI_MODEL", "gpt-6-luna")&lt;/p&gt;

&lt;h1&gt;
  
  
  Assistant personality
&lt;/h1&gt;

&lt;p&gt;SYSTEM_PROMPT = """&lt;br&gt;
You are Nova, a smart and helpful personal AI assistant.&lt;/p&gt;

&lt;p&gt;Your responsibilities:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Give accurate and useful answers.&lt;/li&gt;
&lt;li&gt;Explain complicated topics in simple language.&lt;/li&gt;
&lt;li&gt;Be friendly, respectful and conversational.&lt;/li&gt;
&lt;li&gt;Ask for clarification when a question is unclear.&lt;/li&gt;
&lt;li&gt;Avoid inventing information.&lt;/li&gt;
&lt;li&gt;Admit when you do not know something.&lt;/li&gt;
&lt;li&gt;Help users solve problems step by step.&lt;/li&gt;
&lt;li&gt;Keep simple questions concise.&lt;/li&gt;
&lt;li&gt;Provide detailed explanations when necessary.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You should behave like a thoughtful personal assistant,&lt;br&gt;
not a machine that simply generates random text.&lt;br&gt;
"""&lt;/p&gt;

&lt;h1&gt;
  
  
  Conversation history
&lt;/h1&gt;

&lt;p&gt;conversation = [&lt;br&gt;
    {&lt;br&gt;
        "role": "system",&lt;br&gt;
        "content": SYSTEM_PROMPT&lt;br&gt;
    }&lt;br&gt;
]&lt;/p&gt;

&lt;h1&gt;
  
  
  Display welcome screen
&lt;/h1&gt;

&lt;p&gt;def welcome():&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print("\n" + "=" * 45)
print("          NOVA AI ASSISTANT")
print("=" * 45)

print("Your personal AI assistant")
print("Powered by Python and an AI model")
print("\nCommands:")
print("/reset  - Start a new conversation")
print("/exit   - Close the assistant")

print("=" * 45)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
  
  
  Generate AI response
&lt;/h1&gt;

&lt;p&gt;def generate_response(user_message):&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;conversation.append({
    "role": "user",
    "content": user_message
})

try:

    response = client.responses.create(
        model=MODEL,
        input=conversation
    )

    answer = response.output_text

    conversation.append({
        "role": "assistant",
        "content": answer
    })

    return answer

except Exception as error:

    conversation.pop()

    print("\nSomething went wrong:")
    print(error)

    return None
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
  
  
  Main application
&lt;/h1&gt;

&lt;p&gt;def main():&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;welcome()

while True:

    try:

        user_message = input("\nYou: ").strip()

        if not user_message:
            continue

        # Exit command
        if user_message.lower() == "/exit":

            print("\nNova: Goodbye! See you again.")
            break

        # Reset conversation
        if user_message.lower() == "/reset":

            conversation.clear()

            conversation.append({
                "role": "system",
                "content": SYSTEM_PROMPT
            })

            print("\nNova: Conversation reset!")
            continue

        # Generate answer
        print("\nNova is thinking...")

        answer = generate_response(user_message)

        if answer:
            print("\nNova:", answer)

    except KeyboardInterrupt:

        print("\n\nNova: Assistant stopped.")
        break
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;if &lt;strong&gt;name&lt;/strong&gt; == "&lt;strong&gt;main&lt;/strong&gt;":&lt;br&gt;
    main()&lt;/p&gt;

&lt;p&gt;Save your file&lt;/p&gt;

&lt;p&gt;If you're using Nano:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Press CTRL + O.&lt;/li&gt;
&lt;li&gt;Press Enter to confirm the filename.&lt;/li&gt;
&lt;li&gt;Press CTRL + X to exit.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your Python application is now saved.&lt;/p&gt;

&lt;p&gt;What did we just create?&lt;/p&gt;

&lt;p&gt;Let's understand the important sections.&lt;/p&gt;

&lt;p&gt;The API connection&lt;/p&gt;

&lt;p&gt;client = OpenAI(api_key=API_KEY)&lt;/p&gt;

&lt;p&gt;This initializes the API client.&lt;/p&gt;

&lt;p&gt;It allows our application to communicate with the AI service.&lt;/p&gt;

&lt;p&gt;The assistant's personality&lt;/p&gt;

&lt;p&gt;SYSTEM_PROMPT = """&lt;br&gt;
You are Nova, a smart and helpful personal AI assistant.&lt;br&gt;
"""&lt;/p&gt;

&lt;p&gt;This defines the assistant's behaviour.&lt;/p&gt;

&lt;p&gt;You can change the instructions to create a different personality.&lt;/p&gt;

&lt;p&gt;For example, you could build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A programming tutor.&lt;/li&gt;
&lt;li&gt;A personal study assistant.&lt;/li&gt;
&lt;li&gt;A business assistant.&lt;/li&gt;
&lt;li&gt;A writing assistant.&lt;/li&gt;
&lt;li&gt;A research assistant.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The underlying AI model remains the same, but the instructions influence how it responds.&lt;/p&gt;

&lt;p&gt;The conversation memory&lt;/p&gt;

&lt;p&gt;conversation = [&lt;br&gt;
    {&lt;br&gt;
        "role": "system",&lt;br&gt;
        "content": SYSTEM_PROMPT&lt;br&gt;
    }&lt;br&gt;
]&lt;/p&gt;

&lt;p&gt;This creates the initial conversation history.&lt;/p&gt;

&lt;p&gt;Every user message and assistant response is added to this list.&lt;/p&gt;

&lt;p&gt;When we send the conversation to the model, it receives the available context.&lt;/p&gt;

&lt;p&gt;This is how our assistant can understand follow-up questions.&lt;/p&gt;

&lt;p&gt;However, this is short-term conversational memory, not permanent personal memory.&lt;/p&gt;

&lt;p&gt;The conversation currently exists in the running Python process. Closing the application clears it.&lt;/p&gt;

&lt;p&gt;The AI response&lt;/p&gt;

&lt;p&gt;response = client.responses.create(&lt;br&gt;
    model=MODEL,&lt;br&gt;
    input=conversation&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;This is where the actual AI interaction happens.&lt;/p&gt;

&lt;p&gt;We send the conversation to the Responses API.&lt;/p&gt;

&lt;p&gt;The model processes the information and generates a response.&lt;/p&gt;

&lt;p&gt;We then retrieve the generated text:&lt;/p&gt;

&lt;p&gt;answer = response.output_text&lt;/p&gt;

&lt;p&gt;Finally, the application displays it in Termux.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run Your AI Assistant for the First Time&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now let's test everything.&lt;/p&gt;

&lt;p&gt;Make sure you're still inside the "smart-ai" directory and your virtual environment is activated.&lt;/p&gt;

&lt;p&gt;Run:&lt;/p&gt;

&lt;p&gt;python assistant.py&lt;/p&gt;

&lt;p&gt;If everything is configured correctly, you should see:&lt;/p&gt;

&lt;p&gt;=============================================&lt;/p&gt;

&lt;h1&gt;
  
  
            NOVA AI ASSISTANT
&lt;/h1&gt;

&lt;p&gt;Your personal AI assistant&lt;br&gt;
Powered by Python and an AI model&lt;/p&gt;

&lt;p&gt;Commands:&lt;br&gt;
/reset  - Start a new conversation&lt;/p&gt;

&lt;h1&gt;
  
  
  /exit   - Close the assistant
&lt;/h1&gt;

&lt;p&gt;You:&lt;/p&gt;

&lt;p&gt;Congratulations!&lt;/p&gt;

&lt;p&gt;You have created your first AI-powered assistant using only your Android phone.&lt;/p&gt;

&lt;p&gt;Try asking:&lt;/p&gt;

&lt;p&gt;Hello, who are you?&lt;/p&gt;

&lt;p&gt;Then:&lt;/p&gt;

&lt;p&gt;Explain artificial intelligence to me.&lt;/p&gt;

&lt;p&gt;Now test its conversational memory:&lt;/p&gt;

&lt;p&gt;My name is Daniel.&lt;/p&gt;

&lt;p&gt;Follow that with:&lt;/p&gt;

&lt;p&gt;What is my name?&lt;/p&gt;

&lt;p&gt;The assistant should use the previous message to answer.&lt;/p&gt;

&lt;p&gt;You can also test:&lt;/p&gt;

&lt;p&gt;/reset&lt;/p&gt;

&lt;p&gt;This clears the current conversation.&lt;/p&gt;

&lt;p&gt;And:&lt;/p&gt;

&lt;p&gt;/exit&lt;/p&gt;

&lt;p&gt;This closes the application.&lt;/p&gt;

&lt;p&gt;If the model identifier in the example is not available to your API account, check the provider's current model catalogue and set another supported model:&lt;/p&gt;

&lt;p&gt;export OPENAI_MODEL="YOUR_AVAILABLE_MODEL"&lt;/p&gt;

&lt;p&gt;Then run the application again.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How to Make Your Assistant Smarter&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At this stage, you have a functioning AI assistant.&lt;/p&gt;

&lt;p&gt;But what makes it smart?&lt;/p&gt;

&lt;p&gt;The language model provides the intelligence. Our Python application controls how that intelligence is accessed and presented.&lt;/p&gt;

&lt;p&gt;There are several ways to improve the assistant.&lt;/p&gt;

&lt;p&gt;A. Give It a Better Personality&lt;/p&gt;

&lt;p&gt;You can change the system instructions.&lt;/p&gt;

&lt;p&gt;For example, replace the original personality with:&lt;/p&gt;

&lt;p&gt;SYSTEM_PROMPT = """&lt;br&gt;
You are Atlas, an intelligent learning assistant.&lt;/p&gt;

&lt;p&gt;Your mission is to help people understand&lt;br&gt;
technology, science, programming and everyday problems.&lt;/p&gt;

&lt;p&gt;Always explain difficult concepts using simple examples.&lt;/p&gt;

&lt;p&gt;When teaching programming:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Explain the concept.&lt;/li&gt;
&lt;li&gt;Show a practical example.&lt;/li&gt;
&lt;li&gt;Explain the code.&lt;/li&gt;
&lt;li&gt;Identify common mistakes.&lt;/li&gt;
&lt;li&gt;Give the learner a small exercise.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Never pretend to know something you do not know.&lt;/p&gt;

&lt;p&gt;Prioritize understanding over simply giving answers.&lt;br&gt;
"""&lt;/p&gt;

&lt;p&gt;Now you have a learning-focused assistant.&lt;/p&gt;

&lt;p&gt;B. Give It a Different Name&lt;/p&gt;

&lt;p&gt;Find:&lt;/p&gt;

&lt;p&gt;print("NOVA AI ASSISTANT")&lt;/p&gt;

&lt;p&gt;Replace it with:&lt;/p&gt;

&lt;p&gt;print("ATLAS AI ASSISTANT")&lt;/p&gt;

&lt;p&gt;You can also change the name inside the system instructions.&lt;/p&gt;

&lt;p&gt;C. Limit Conversation History&lt;/p&gt;

&lt;p&gt;Our current application sends the entire conversation history with every request.&lt;/p&gt;

&lt;p&gt;This is useful for understanding previous messages, but long conversations can become expensive and eventually exceed the model's context limit.&lt;/p&gt;

&lt;p&gt;A simple improvement is to retain only the most recent messages.&lt;/p&gt;

&lt;p&gt;For example, after adding an assistant response, you could use:&lt;/p&gt;

&lt;p&gt;conversation[:] = [&lt;br&gt;
    conversation[0]&lt;br&gt;
] + conversation[-20:]&lt;/p&gt;

&lt;p&gt;This retains the system instructions and the latest 20 conversation messages.&lt;/p&gt;

&lt;p&gt;For a production application, history management should be more careful so that user and assistant messages remain properly paired.&lt;/p&gt;

&lt;p&gt;D. Make It Remember Conversations Between Sessions&lt;/p&gt;

&lt;p&gt;Currently, closing Termux clears the conversation.&lt;/p&gt;

&lt;p&gt;You can extend the project by saving conversation history to a local JSON file.&lt;/p&gt;

&lt;p&gt;Python includes a built-in library for this:&lt;/p&gt;

&lt;p&gt;import json&lt;/p&gt;

&lt;p&gt;You could save the conversation using:&lt;/p&gt;

&lt;p&gt;with open("memory.json", "w") as file:&lt;br&gt;
    json.dump(conversation, file, indent=4)&lt;/p&gt;

&lt;p&gt;And load it when the application starts:&lt;/p&gt;

&lt;p&gt;with open("memory.json", "r") as file:&lt;br&gt;
    conversation = json.load(file)&lt;/p&gt;

&lt;p&gt;These are basic examples. A complete implementation should handle missing files, invalid JSON, reset behaviour and private data.&lt;/p&gt;

&lt;p&gt;Also, understand the distinction between conversation history and long-term memory.&lt;/p&gt;

&lt;p&gt;Conversation history stores what was said.&lt;/p&gt;

&lt;p&gt;A more advanced memory system identifies useful information, stores it separately and retrieves relevant details when needed.&lt;/p&gt;

&lt;p&gt;You could eventually give your assistant the ability to remember a user's preferred programming language, ongoing projects or learning goals.&lt;/p&gt;

&lt;p&gt;However, users should have control over what is stored and the ability to delete it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Troubleshooting Common Problems&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If something doesn't work, don't immediately assume your phone is incompatible.&lt;/p&gt;

&lt;p&gt;Here are some common problems.&lt;/p&gt;

&lt;p&gt;Problem 1: Python command not found&lt;/p&gt;

&lt;p&gt;You probably haven't installed Python correctly.&lt;/p&gt;

&lt;p&gt;Run:&lt;/p&gt;

&lt;p&gt;pkg install python&lt;/p&gt;

&lt;p&gt;Then check:&lt;/p&gt;

&lt;p&gt;python --version&lt;/p&gt;

&lt;p&gt;Problem 2: No module named openai&lt;/p&gt;

&lt;p&gt;The Python library may not have been installed in your active environment.&lt;/p&gt;

&lt;p&gt;Activate your virtual environment:&lt;/p&gt;

&lt;p&gt;source venv/bin/activate&lt;/p&gt;

&lt;p&gt;Install the library:&lt;/p&gt;

&lt;p&gt;python -m pip install openai&lt;/p&gt;

&lt;p&gt;Problem 3: API key not found&lt;/p&gt;

&lt;p&gt;Check whether your environment variable exists:&lt;/p&gt;

&lt;p&gt;echo $OPENAI_API_KEY&lt;/p&gt;

&lt;p&gt;If it is empty, configure your API key again:&lt;/p&gt;

&lt;p&gt;export OPENAI_API_KEY="YOUR_API_KEY"&lt;/p&gt;

&lt;p&gt;Problem 4: Authentication error&lt;/p&gt;

&lt;p&gt;Check that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your API key is correct.&lt;/li&gt;
&lt;li&gt;Your key has not been revoked.&lt;/li&gt;
&lt;li&gt;Your API account has access.&lt;/li&gt;
&lt;li&gt;Your account has the necessary billing configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Never share your API key when requesting technical assistance.&lt;/p&gt;

&lt;p&gt;Problem 5: Model not found&lt;/p&gt;

&lt;p&gt;The model name might not be available to your account.&lt;/p&gt;

&lt;p&gt;Check the provider's current model documentation and select an accessible model.&lt;/p&gt;

&lt;p&gt;Problem 6: Connection errors&lt;/p&gt;

&lt;p&gt;The application needs internet access.&lt;/p&gt;

&lt;p&gt;Check your connection and try again.&lt;/p&gt;

&lt;p&gt;Remember that Termux runs on Android, so Android may also restrict background processes or terminate long-running sessions.&lt;/p&gt;

&lt;p&gt;For this beginner project, keep Termux open while using the assistant.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What Can You Build Next?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You now have a basic AI assistant.&lt;/p&gt;

&lt;p&gt;But this is only the beginning.&lt;/p&gt;

&lt;p&gt;The interesting part of software development is taking a working foundation and gradually introducing more useful features.&lt;/p&gt;

&lt;p&gt;Here are some ideas.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Voice assistant&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Add speech recognition so that users can speak instead of typing.&lt;/p&gt;

&lt;p&gt;You could also add text-to-speech so the assistant can read responses aloud.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Web interface&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Instead of communicating through the terminal, create a browser-based interface using Python and a lightweight web framework.&lt;/p&gt;

&lt;p&gt;You could build a proper chat interface with message bubbles, a conversation sidebar and a responsive design.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Document assistant&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Allow users to provide documents and ask questions about their contents.&lt;/p&gt;

&lt;p&gt;For example, a student could provide study notes and ask the assistant to explain particular sections.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Programming assistant&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Create a specialized assistant that explains programming errors, reviews code and helps beginners understand unfamiliar concepts.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Long-term memory&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Develop a system that stores useful information between conversations.&lt;/p&gt;

&lt;p&gt;You could use SQLite or another database to organize saved information.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tool integration&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Eventually, your assistant could perform useful actions rather than simply answering questions.&lt;/p&gt;

&lt;p&gt;For example, it could calculate values, organize information or interact with approved external services.&lt;/p&gt;

&lt;p&gt;This is an important step towards building an AI agent.&lt;/p&gt;

&lt;p&gt;An AI chatbot responds to messages.&lt;/p&gt;

&lt;p&gt;An AI agent can also use tools to complete tasks, subject to the permissions and rules you give it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Can You Build an AI That Works Completely Offline?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Yes, but it is a different project.&lt;/p&gt;

&lt;p&gt;The assistant we created uses a remotely hosted model.&lt;/p&gt;

&lt;p&gt;If your internet connection disappears, it cannot generate new AI responses.&lt;/p&gt;

&lt;p&gt;A completely offline assistant requires a language model that can run locally on your Android device.&lt;/p&gt;

&lt;p&gt;Projects such as llama.cpp explore running language models on different hardware platforms.&lt;/p&gt;

&lt;p&gt;However, local AI has additional requirements.&lt;/p&gt;

&lt;p&gt;Your device needs sufficient:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Available storage.&lt;/li&gt;
&lt;li&gt;RAM.&lt;/li&gt;
&lt;li&gt;Processing power.&lt;/li&gt;
&lt;li&gt;Compatible software.&lt;/li&gt;
&lt;li&gt;Battery capacity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Larger models generally require more resources.&lt;/p&gt;

&lt;p&gt;A small quantized model may be practical on some devices, while a larger model might be extremely slow or fail to run because of memory limitations.&lt;/p&gt;

&lt;p&gt;Termux also has Android-specific compatibility considerations.&lt;/p&gt;

&lt;p&gt;Don't assume that every Linux AI installation tutorial will work on Android without modification.&lt;/p&gt;

&lt;p&gt;For a beginner, starting with an API-powered assistant is usually easier.&lt;/p&gt;

&lt;p&gt;Once you understand how the application works, you can experiment with local inference.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Final Thoughts: You Don't Need an Expensive Computer to Start Building AI&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One of the biggest barriers to learning software development is the belief that you need expensive equipment before you can begin.&lt;/p&gt;

&lt;p&gt;That's not always true.&lt;/p&gt;

&lt;p&gt;An Android phone can provide a surprisingly useful development environment.&lt;/p&gt;

&lt;p&gt;With Termux and Python, you can experiment with programming, create small applications and communicate with powerful AI models.&lt;/p&gt;

&lt;p&gt;Of course, a phone has limitations. A larger screen and more powerful hardware make complex development easier.&lt;/p&gt;

&lt;p&gt;But the most important thing is getting started.&lt;/p&gt;

&lt;p&gt;You now understand how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prepare a Python development environment.&lt;/li&gt;
&lt;li&gt;Install programming libraries.&lt;/li&gt;
&lt;li&gt;Connect an application to an AI API.&lt;/li&gt;
&lt;li&gt;Create an assistant personality.&lt;/li&gt;
&lt;li&gt;Manage conversational context.&lt;/li&gt;
&lt;li&gt;Handle basic errors.&lt;/li&gt;
&lt;li&gt;Extend an AI application with additional features.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't just copy the code and stop there.&lt;/p&gt;

&lt;p&gt;Change the assistant's name. Experiment with its personality. Add new commands. Try saving conversations. Break something, investigate it and fix it.&lt;/p&gt;

&lt;p&gt;The best way to become a developer is not simply to follow tutorials.&lt;/p&gt;

&lt;p&gt;It is to understand what you're building and gradually become confident enough to create your own solutions.&lt;/p&gt;

&lt;p&gt;If you managed to build Nova, you've already taken your first practical step.&lt;/p&gt;

&lt;p&gt;Your phone is not just a device for consuming technology. It can also be a tool for creating it.&lt;/p&gt;




&lt;p&gt;A little more inspiration&lt;/p&gt;

&lt;p&gt;I believe technology becomes most valuable when it helps people solve real problems.&lt;/p&gt;

&lt;p&gt;That is also an idea behind AfriWidget, a project focused on building practical digital tools for education, productivity and everyday needs.&lt;/p&gt;

&lt;p&gt;If you're interested in seeing how relatively simple web technologies can become useful products, take a look at "AfriWidget" (&lt;a href="https://afriwidget.com" rel="noopener noreferrer"&gt;https://afriwidget.com&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;You don't always need to build the biggest technology to make a meaningful difference. Sometimes, solving one problem properly is a great place to start.&lt;/p&gt;




&lt;p&gt;Further Reading&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Termux official documentation" (&lt;a href="https://termux.dev/" rel="noopener noreferrer"&gt;https://termux.dev/&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;"Termux GitHub repository" (&lt;a href="https://github.com/termux/termux-app" rel="noopener noreferrer"&gt;https://github.com/termux/termux-app&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;"Python documentation" (&lt;a href="https://docs.python.org/3/" rel="noopener noreferrer"&gt;https://docs.python.org/3/&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;"OpenAI API documentation" (&lt;a href="https://platform.openai.com/docs/" rel="noopener noreferrer"&gt;https://platform.openai.com/docs/&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;"OpenAI API pricing" (&lt;a href="https://platform.openai.com/pricing/" rel="noopener noreferrer"&gt;https://platform.openai.com/pricing/&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;"llama.cpp GitHub repository" (&lt;a href="https://github.com/ggml-org/llama.cpp" rel="noopener noreferrer"&gt;https://github.com/ggml-org/llama.cpp&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Disclaimer: This tutorial demonstrates an API-powered AI assistant for educational purposes. API availability, model identifiers, account requirements and pricing can change. Keep your API credentials private and review your provider's current terms before using the application.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <item>
      <title>AI Can Write Your Code, But Can You Still Understand It? The Developer's Guide to Avoiding AI Dependency</title>
      <dc:creator>Obed Avorlenu</dc:creator>
      <pubDate>Fri, 30 Oct 2026 00:57:09 +0000</pubDate>
      <link>https://dev.to/obed_avorlenu/ai-can-write-your-code-but-can-you-still-understand-it-the-developers-guide-to-avoiding-ai-58pp</link>
      <guid>https://dev.to/obed_avorlenu/ai-can-write-your-code-but-can-you-still-understand-it-the-developers-guide-to-avoiding-ai-58pp</guid>
      <description>&lt;p&gt;Artificial intelligence has changed software development in ways that would have seemed almost impossible a few years ago.&lt;/p&gt;

&lt;p&gt;Today, someone with little programming experience can describe an application, ask an AI assistant to generate the code, and receive something that looks like a functioning product within minutes.&lt;/p&gt;

&lt;p&gt;A developer can generate an entire website without manually writing every component. Errors that previously required hours of searching can sometimes be explained in seconds. Documentation, debugging suggestions, test cases and even architectural recommendations are becoming easier to access.&lt;/p&gt;

&lt;p&gt;This is genuinely exciting.&lt;/p&gt;

&lt;p&gt;However, there is an important question that deserves more attention:&lt;/p&gt;

&lt;p&gt;Are we becoming better developers, or are we simply becoming better at asking AI to write code?&lt;/p&gt;

&lt;p&gt;These two things are not necessarily the same.&lt;/p&gt;

&lt;p&gt;AI can increase how quickly we produce software, but speed alone does not guarantee understanding, reliability or technical growth.&lt;/p&gt;

&lt;p&gt;A developer who can generate 5,000 lines of code but cannot explain how those lines work may have a functioning application without having the knowledge required to maintain it.&lt;/p&gt;

&lt;p&gt;This article explores the difference between using AI as a development partner and becoming dependent on it. More importantly, it provides a practical approach to using AI without sacrificing the skills that make someone a capable developer.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The New Reality of Software Development&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There was a time when learning to develop software required spending considerable time understanding programming languages, reading documentation, experimenting with code and troubleshooting errors.&lt;/p&gt;

&lt;p&gt;If you wanted to build a calculator, you needed to understand how inputs worked, how calculations were performed and how results were displayed.&lt;/p&gt;

&lt;p&gt;If something broke, you had to investigate.&lt;/p&gt;

&lt;p&gt;You might spend an entire afternoon discovering that a small mistake in a conditional statement was responsible for an unexpected result.&lt;/p&gt;

&lt;p&gt;Today, the experience is different.&lt;/p&gt;

&lt;p&gt;You can explain your requirements to an AI assistant and ask it to build the calculator. It might generate the HTML, CSS and JavaScript, including the calculation logic and interface.&lt;/p&gt;

&lt;p&gt;This changes the amount of manual work involved.&lt;/p&gt;

&lt;p&gt;But it also changes the learning process.&lt;/p&gt;

&lt;p&gt;Traditionally, developers often learned through the struggle of constructing something themselves. They encountered problems, investigated them and gradually developed an understanding of how different parts of a system interacted.&lt;/p&gt;

&lt;p&gt;AI can remove some of that struggle.&lt;/p&gt;

&lt;p&gt;That is not automatically bad. Developers should not have to waste hours solving problems that can be explained quickly.&lt;/p&gt;

&lt;p&gt;The challenge is recognising which struggles are unnecessary and which ones contribute to your development as an engineer.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spending three hours searching for a missing semicolon may not be particularly valuable.&lt;/li&gt;
&lt;li&gt;Understanding why a calculation produces an incorrect result is valuable.&lt;/li&gt;
&lt;li&gt;Asking AI to explain an unfamiliar API can accelerate learning.&lt;/li&gt;
&lt;li&gt;Copying an API implementation without understanding its security implications can create serious problems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The objective should not be to avoid AI.&lt;/p&gt;

&lt;p&gt;It should be to use AI in a way that makes you more capable rather than less involved.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Hidden Problem: Understanding Debt&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Developers are familiar with technical debt.&lt;/p&gt;

&lt;p&gt;Technical debt occurs when shortcuts or imperfect implementation decisions create additional work that must be addressed later.&lt;/p&gt;

&lt;p&gt;There is another problem worth recognising: understanding debt.&lt;/p&gt;

&lt;p&gt;Understanding debt develops when you accumulate code, tools, dependencies and architectural decisions that you cannot adequately explain.&lt;/p&gt;

&lt;p&gt;Imagine asking AI to build a personal finance application.&lt;/p&gt;

&lt;p&gt;The first version looks impressive. It has a dashboard, charts, transaction history, calculations and a responsive interface.&lt;/p&gt;

&lt;p&gt;You test a few features, and everything appears to work.&lt;/p&gt;

&lt;p&gt;Then you request additional functionality.&lt;/p&gt;

&lt;p&gt;AI modifies several files. Something that previously worked stops functioning. You ask it to fix the problem, and it changes another section.&lt;/p&gt;

&lt;p&gt;After several iterations, you have a growing application.&lt;/p&gt;

&lt;p&gt;But you are no longer entirely certain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which function controls the transaction calculations?&lt;/li&gt;
&lt;li&gt;Where is user data stored?&lt;/li&gt;
&lt;li&gt;What happens when an input is empty?&lt;/li&gt;
&lt;li&gt;How are incorrect values handled?&lt;/li&gt;
&lt;li&gt;Which components depend on the modified function?&lt;/li&gt;
&lt;li&gt;Whether the latest changes introduced another problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You have accumulated functionality faster than you have accumulated understanding.&lt;/p&gt;

&lt;p&gt;This is understanding debt.&lt;/p&gt;

&lt;p&gt;It can become particularly expensive when maintaining an application, investigating unexpected behaviour or introducing new features.&lt;/p&gt;

&lt;p&gt;The problem is not that AI wrote the code. The problem is that the developer accepted implementation decisions without developing enough knowledge to evaluate them.&lt;/p&gt;

&lt;p&gt;Why understanding debt matters&lt;/p&gt;

&lt;p&gt;Software is rarely finished when the first version works.&lt;/p&gt;

&lt;p&gt;Real applications evolve.&lt;/p&gt;

&lt;p&gt;Users discover problems. Requirements change. Browsers introduce new behaviours. Dependencies receive updates. New features interact with existing ones.&lt;/p&gt;

&lt;p&gt;A developer who understands the system can investigate these changes.&lt;/p&gt;

&lt;p&gt;A developer who depends entirely on AI may have to ask the same tool to explain the system it previously generated.&lt;/p&gt;

&lt;p&gt;That creates a cycle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;AI generates code.&lt;/li&gt;
&lt;li&gt;The developer encounters a problem.&lt;/li&gt;
&lt;li&gt;AI modifies the code.&lt;/li&gt;
&lt;li&gt;Another problem appears.&lt;/li&gt;
&lt;li&gt;The developer requests another modification.&lt;/li&gt;
&lt;li&gt;The codebase becomes increasingly difficult to understand.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is not an inevitable outcome of AI-assisted development, but it is a pattern worth avoiding.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;AI Should Accelerate Your Thinking, Not Replace It&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One of the most useful ways to approach AI-assisted development is to change the questions you ask.&lt;/p&gt;

&lt;p&gt;Instead of treating AI as a machine that produces finished code, treat it as a technical assistant that helps you understand problems and evaluate possible solutions.&lt;/p&gt;

&lt;p&gt;Consider two approaches.&lt;/p&gt;

&lt;p&gt;Approach A: Asking AI to do everything&lt;/p&gt;

&lt;p&gt;"Build me a complete student grading calculator with a modern interface."&lt;/p&gt;

&lt;p&gt;AI generates the application.&lt;/p&gt;

&lt;p&gt;You copy the code, open the browser and check whether it looks correct.&lt;/p&gt;

&lt;p&gt;If something goes wrong, you ask AI to fix it.&lt;/p&gt;

&lt;p&gt;Approach B: Using AI as a development partner&lt;/p&gt;

&lt;p&gt;"I want to build a student grading calculator. First, help me identify the calculation requirements, possible input errors and edge cases. Explain the calculation logic before implementation."&lt;/p&gt;

&lt;p&gt;You review the proposed approach.&lt;/p&gt;

&lt;p&gt;Then you ask AI to implement the calculation function.&lt;/p&gt;

&lt;p&gt;Next, you request test cases and examine the results.&lt;/p&gt;

&lt;p&gt;Finally, you build or review the interface and verify that it displays the correct information.&lt;/p&gt;

&lt;p&gt;The second approach involves more deliberate thinking.&lt;/p&gt;

&lt;p&gt;It also produces something more valuable: an understanding of the application you are building.&lt;/p&gt;

&lt;p&gt;You do not necessarily need to write every line yourself.&lt;/p&gt;

&lt;p&gt;You need to understand enough to make responsible decisions about the code.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Five Skills Developers Should Never Outsource Completely&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;AI can assist with almost every stage of development. However, there are fundamental skills that developers should continue strengthening.&lt;/p&gt;

&lt;p&gt;4.1 Problem-solving&lt;/p&gt;

&lt;p&gt;Programming is not fundamentally about typing instructions into an editor.&lt;/p&gt;

&lt;p&gt;It is about solving problems through logical and structured thinking.&lt;/p&gt;

&lt;p&gt;Before writing code, you should be able to identify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What problem are you solving?&lt;/li&gt;
&lt;li&gt;Who experiences the problem?&lt;/li&gt;
&lt;li&gt;What information is required?&lt;/li&gt;
&lt;li&gt;What should happen when the information changes?&lt;/li&gt;
&lt;li&gt;What should happen when the information is incorrect?&lt;/li&gt;
&lt;li&gt;What would a successful outcome look like?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These questions apply whether you are building a simple calculator or a large software platform.&lt;/p&gt;

&lt;p&gt;AI can suggest solutions, but you should remain responsible for understanding the problem.&lt;/p&gt;

&lt;p&gt;A useful exercise is to describe your proposed solution in plain English before asking AI to implement it.&lt;/p&gt;

&lt;p&gt;If you cannot explain what the application should do, you may not yet have enough clarity to evaluate the generated implementation.&lt;/p&gt;

&lt;p&gt;4.2 Debugging&lt;/p&gt;

&lt;p&gt;Debugging is one of the most important skills in software development.&lt;/p&gt;

&lt;p&gt;An application can look perfectly functional while producing incorrect results behind the scenes.&lt;/p&gt;

&lt;p&gt;For example, consider a grading calculator.&lt;/p&gt;

&lt;p&gt;A student enters three scores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assignment: 80%&lt;/li&gt;
&lt;li&gt;Class test: 70%&lt;/li&gt;
&lt;li&gt;Examination: 60%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application displays a final score.&lt;/p&gt;

&lt;p&gt;But what if the assignment contributes 20% of the final grade, the class test contributes 30%, and the examination contributes 50%?&lt;/p&gt;

&lt;p&gt;The correct calculation is:&lt;/p&gt;

&lt;p&gt;(80 × 0.20) + (70 × 0.30) + (60 × 0.50)&lt;/p&gt;

&lt;p&gt;The result is 67%.&lt;/p&gt;

&lt;p&gt;If the application simply calculates the average of the three scores, it produces 70%.&lt;/p&gt;

&lt;p&gt;Both results look reasonable. Only one follows the specified weighting system.&lt;/p&gt;

&lt;p&gt;This illustrates why debugging is more than fixing syntax errors.&lt;/p&gt;

&lt;p&gt;It requires understanding the intended behaviour and comparing it with what the application actually does.&lt;/p&gt;

&lt;p&gt;AI can help identify the mistake, but you should learn to verify the underlying logic.&lt;/p&gt;

&lt;p&gt;4.3 Reading code&lt;/p&gt;

&lt;p&gt;Writing code is only part of development.&lt;/p&gt;

&lt;p&gt;You also need to understand code written by other people, libraries, frameworks and AI systems.&lt;/p&gt;

&lt;p&gt;Start by becoming comfortable reading:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Variables and functions.&lt;/li&gt;
&lt;li&gt;Conditional statements.&lt;/li&gt;
&lt;li&gt;Loops.&lt;/li&gt;
&lt;li&gt;Event listeners.&lt;/li&gt;
&lt;li&gt;Objects and arrays.&lt;/li&gt;
&lt;li&gt;Asynchronous operations.&lt;/li&gt;
&lt;li&gt;Error handling.&lt;/li&gt;
&lt;li&gt;Data structures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You do not have to understand every advanced programming concept immediately.&lt;/p&gt;

&lt;p&gt;However, you should gradually become comfortable following the execution of a program.&lt;/p&gt;

&lt;p&gt;When AI generates a function, ask it to explain the function in plain English.&lt;/p&gt;

&lt;p&gt;Then try to explain it yourself without looking at the explanation.&lt;/p&gt;

&lt;p&gt;This small exercise can make a considerable difference over time.&lt;/p&gt;

&lt;p&gt;4.4 Testing and verification&lt;/p&gt;

&lt;p&gt;A program working once does not mean it works correctly.&lt;/p&gt;

&lt;p&gt;A calculator that produces the right answer for one input might fail when the user enters zero, a decimal, a negative number or an unusually large value.&lt;/p&gt;

&lt;p&gt;Developers should learn to test more than the expected scenario.&lt;/p&gt;

&lt;p&gt;For example, when building a percentage calculator, test:&lt;/p&gt;

&lt;p&gt;Test case| Expected behaviour&lt;br&gt;
Normal values| Correct percentage&lt;br&gt;
Empty input| Clear validation message&lt;br&gt;
Zero as the total| Prevent division by zero&lt;br&gt;
Negative values| Reject or handle appropriately&lt;br&gt;
Decimal values| Calculate accurately&lt;br&gt;
Text instead of numbers| Reject invalid input&lt;br&gt;
Very large values| Handle without unexpected behaviour&lt;/p&gt;

&lt;p&gt;AI can generate test cases, but the developer should verify that they represent the actual requirements.&lt;/p&gt;

&lt;p&gt;Testing should be part of development, not something added only after users report problems.&lt;/p&gt;

&lt;p&gt;4.5 Understanding security&lt;/p&gt;

&lt;p&gt;Security is particularly important because AI-generated code can look convincing even when it contains vulnerabilities.&lt;/p&gt;

&lt;p&gt;For example, an application might accept user input and insert it directly into HTML.&lt;/p&gt;

&lt;p&gt;Without appropriate handling, this can create opportunities for cross-site scripting.&lt;/p&gt;

&lt;p&gt;Other areas requiring attention include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication and authorization.&lt;/li&gt;
&lt;li&gt;Password storage.&lt;/li&gt;
&lt;li&gt;API key protection.&lt;/li&gt;
&lt;li&gt;Input validation.&lt;/li&gt;
&lt;li&gt;File uploads.&lt;/li&gt;
&lt;li&gt;Database queries.&lt;/li&gt;
&lt;li&gt;Cross-origin resource sharing.&lt;/li&gt;
&lt;li&gt;Sensitive information exposure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Never assume that generated code is secure simply because it runs successfully.&lt;/p&gt;

&lt;p&gt;Security requires deliberate review, testing and an understanding of the environment in which the application operates.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A Practical Workflow for Building With AI&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You do not need to abandon AI-generated code to become a better developer.&lt;/p&gt;

&lt;p&gt;Instead, introduce a process that keeps you involved throughout development.&lt;/p&gt;

&lt;p&gt;Here is a workflow that works well for small and medium-sized projects.&lt;/p&gt;

&lt;p&gt;Step 1: Define the problem before requesting code&lt;/p&gt;

&lt;p&gt;Start with the purpose of the application.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;"I want to build a tool that helps students calculate their weighted academic scores."&lt;/p&gt;

&lt;p&gt;Identify the users, expected inputs, calculation rules and desired output.&lt;/p&gt;

&lt;p&gt;Do not begin with colours, animations or visual effects.&lt;/p&gt;

&lt;p&gt;A beautiful interface cannot compensate for incorrect functionality.&lt;/p&gt;

&lt;p&gt;Step 2: Ask for the implementation plan&lt;/p&gt;

&lt;p&gt;Before generating code, ask AI to explain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The proposed structure.&lt;/li&gt;
&lt;li&gt;The functions required.&lt;/li&gt;
&lt;li&gt;How information will move through the application.&lt;/li&gt;
&lt;li&gt;Potential edge cases.&lt;/li&gt;
&lt;li&gt;Possible technical limitations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Review the plan.&lt;/p&gt;

&lt;p&gt;If something is unclear, ask questions before implementation.&lt;/p&gt;

&lt;p&gt;Step 3: Build in manageable sections&lt;/p&gt;

&lt;p&gt;Avoid requesting an entire complex application in one enormous prompt.&lt;/p&gt;

&lt;p&gt;Divide the project into meaningful components.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Calculation logic.&lt;/li&gt;
&lt;li&gt;Input validation.&lt;/li&gt;
&lt;li&gt;Results presentation.&lt;/li&gt;
&lt;li&gt;User interface.&lt;/li&gt;
&lt;li&gt;Error handling.&lt;/li&gt;
&lt;li&gt;Testing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This makes it easier to identify problems and understand individual components.&lt;/p&gt;

&lt;p&gt;It also reduces the risk of unnecessary modifications affecting unrelated parts of the application.&lt;/p&gt;

&lt;p&gt;Step 4: Request explanations, not just solutions&lt;/p&gt;

&lt;p&gt;When AI generates an unfamiliar function, ask:&lt;/p&gt;

&lt;p&gt;"Explain how this function works, what information it receives, what it returns and what could cause it to fail."&lt;/p&gt;

&lt;p&gt;Then examine the explanation against the actual code.&lt;/p&gt;

&lt;p&gt;Do not blindly trust explanations either. Generated explanations can also contain mistakes.&lt;/p&gt;

&lt;p&gt;Step 5: Test independently&lt;/p&gt;

&lt;p&gt;Create your own test cases.&lt;/p&gt;

&lt;p&gt;Do not rely exclusively on AI saying that the implementation is correct.&lt;/p&gt;

&lt;p&gt;For mathematical applications, manually calculate a few expected answers.&lt;/p&gt;

&lt;p&gt;For web applications, test different browsers, screen sizes and input combinations.&lt;/p&gt;

&lt;p&gt;For applications that store information, check what happens when users refresh the page or clear their saved data.&lt;/p&gt;

&lt;p&gt;Step 6: Review every important change&lt;/p&gt;

&lt;p&gt;When AI modifies an existing project, examine what changed.&lt;/p&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which files were modified?&lt;/li&gt;
&lt;li&gt;Why were they modified?&lt;/li&gt;
&lt;li&gt;Did the changes affect existing functionality?&lt;/li&gt;
&lt;li&gt;Were any dependencies introduced?&lt;/li&gt;
&lt;li&gt;Were existing features removed?&lt;/li&gt;
&lt;li&gt;What tests were performed?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is particularly important when maintaining an application that already has real users.&lt;/p&gt;

&lt;p&gt;Step 7: Keep learning while building&lt;/p&gt;

&lt;p&gt;When you encounter something unfamiliar, take a few minutes to understand it.&lt;/p&gt;

&lt;p&gt;You do not need to stop development and study an entire programming textbook.&lt;/p&gt;

&lt;p&gt;Learn the specific concept required to understand the implementation.&lt;/p&gt;

&lt;p&gt;Over time, these small learning opportunities accumulate into practical experience.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How to Write Better Prompts Without Becoming a Prompt Engineer&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There is a lot of discussion about prompt engineering.&lt;/p&gt;

&lt;p&gt;However, you do not need complicated prompts to get useful results.&lt;/p&gt;

&lt;p&gt;Clear communication is often more important than prompt length.&lt;/p&gt;

&lt;p&gt;Compare these examples.&lt;/p&gt;

&lt;p&gt;Weak prompt:&lt;/p&gt;

&lt;p&gt;"Make my website better."&lt;/p&gt;

&lt;p&gt;The request is too broad. The AI must guess what better means.&lt;/p&gt;

&lt;p&gt;More useful prompt:&lt;/p&gt;

&lt;p&gt;"Review this website for accessibility, mobile usability and performance. Identify the five most important problems, explain why each matters and recommend improvements without changing the existing design."&lt;/p&gt;

&lt;p&gt;The second prompt establishes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The areas to investigate.&lt;/li&gt;
&lt;li&gt;The expected output.&lt;/li&gt;
&lt;li&gt;The boundaries of the task.&lt;/li&gt;
&lt;li&gt;The need for explanations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also use a few simple prompting habits.&lt;/p&gt;

&lt;p&gt;Ask for assumptions.&lt;/p&gt;

&lt;p&gt;If your requirements are incomplete, ask AI to identify what it is assuming.&lt;/p&gt;

&lt;p&gt;Request alternatives.&lt;/p&gt;

&lt;p&gt;Ask for two or three approaches when the technical decision has meaningful trade-offs.&lt;/p&gt;

&lt;p&gt;Set boundaries.&lt;/p&gt;

&lt;p&gt;Specify which files, features or components should remain unchanged.&lt;/p&gt;

&lt;p&gt;Request verification.&lt;/p&gt;

&lt;p&gt;Ask what tests would demonstrate that the implementation works correctly.&lt;/p&gt;

&lt;p&gt;Challenge the result.&lt;/p&gt;

&lt;p&gt;If something looks suspicious, ask AI to investigate it rather than immediately accepting its explanation.&lt;/p&gt;

&lt;p&gt;Good prompting is not about writing the longest possible instruction.&lt;/p&gt;

&lt;p&gt;It is about communicating the problem clearly enough to receive a useful response.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Why Small Projects Are Still Important in the Age of AI&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One mistake aspiring developers make is believing that they must build something enormous to gain meaningful experience.&lt;/p&gt;

&lt;p&gt;They want to create a social network, a complete e-commerce platform or an advanced artificial intelligence application.&lt;/p&gt;

&lt;p&gt;There is nothing wrong with ambitious projects.&lt;/p&gt;

&lt;p&gt;However, small projects provide excellent opportunities to understand fundamental programming concepts.&lt;/p&gt;

&lt;p&gt;Consider building these five applications:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A percentage calculator&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Learn mathematical operations, input validation and event handling.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A personal expense tracker&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Learn arrays, objects, data storage and basic data manipulation.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A study timetable generator&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Learn conditional logic, date handling and user interaction.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A simple quiz application&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Learn state management, arrays, scoring and conditional rendering.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An academic performance tracker&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Learn data persistence, calculations, historical records and visual representation.&lt;/p&gt;

&lt;p&gt;These projects may appear simple, but they introduce concepts used in larger applications.&lt;/p&gt;

&lt;p&gt;The important part is not how impressive the finished interface looks.&lt;/p&gt;

&lt;p&gt;It is whether you understand how the application works.&lt;/p&gt;

&lt;p&gt;Try building a small project with AI assistance, then challenge yourself to recreate one of its main features without asking AI to generate the implementation.&lt;/p&gt;

&lt;p&gt;That exercise can reveal exactly which concepts you understand and which ones still need attention.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Learning to Build Software That Actually Helps People&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There is another dimension of software development that deserves attention.&lt;/p&gt;

&lt;p&gt;Developers sometimes become so focused on programming languages, frameworks, animations and technical achievements that they forget the reason software exists.&lt;/p&gt;

&lt;p&gt;Software should solve problems.&lt;/p&gt;

&lt;p&gt;A technically impressive application that nobody needs is not necessarily a successful product.&lt;/p&gt;

&lt;p&gt;A simple application that saves someone time, prevents a mistake or makes a difficult process easier can be far more valuable.&lt;/p&gt;

&lt;p&gt;Consider a student trying to understand their academic performance.&lt;/p&gt;

&lt;p&gt;They might need to calculate weighted marks, understand their examination results, identify subjects requiring more attention or track their improvement over time.&lt;/p&gt;

&lt;p&gt;A developer could build several tools to address these needs.&lt;/p&gt;

&lt;p&gt;However, the real challenge is not simply implementing mathematical formulas.&lt;/p&gt;

&lt;p&gt;It is understanding what students actually need.&lt;/p&gt;

&lt;p&gt;A useful educational tool should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Explain its calculation method.&lt;/li&gt;
&lt;li&gt;Accept the correct inputs.&lt;/li&gt;
&lt;li&gt;Validate incorrect entries.&lt;/li&gt;
&lt;li&gt;Present understandable results.&lt;/li&gt;
&lt;li&gt;Avoid misleading claims.&lt;/li&gt;
&lt;li&gt;Help users make informed decisions.&lt;/li&gt;
&lt;li&gt;Work properly on mobile devices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where the distinction between building software and building useful software becomes important.&lt;/p&gt;

&lt;p&gt;The same principle applies to financial tools, productivity applications, business software and health-related calculators.&lt;/p&gt;

&lt;p&gt;Technical ability helps you create the product.&lt;/p&gt;

&lt;p&gt;Understanding the user helps you create something worth using.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Importance of Building for People With Different Levels of Technical Knowledge&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Developers often build applications for people who do not understand how those applications work.&lt;/p&gt;

&lt;p&gt;A developer understands what a percentage means in a particular calculation.&lt;/p&gt;

&lt;p&gt;A student using the application might only want to know whether they have passed.&lt;/p&gt;

&lt;p&gt;A developer understands how a form validates an input.&lt;/p&gt;

&lt;p&gt;A first-time user might simply wonder why the application is refusing to accept their entry.&lt;/p&gt;

&lt;p&gt;Good software accounts for these differences.&lt;/p&gt;

&lt;p&gt;Consider a poorly designed calculator.&lt;/p&gt;

&lt;p&gt;A user enters their scores, presses a button and receives a number without any explanation.&lt;/p&gt;

&lt;p&gt;The calculation might be correct, but the user is left wondering what the result means.&lt;/p&gt;

&lt;p&gt;A more thoughtful application explains the result, provides relevant context and makes the next step clear.&lt;/p&gt;

&lt;p&gt;This does not mean every application needs artificial intelligence, complex dashboards or dozens of features.&lt;/p&gt;

&lt;p&gt;Sometimes the best improvement is a simple explanation beside a result.&lt;/p&gt;

&lt;p&gt;Accessibility, understandable language, mobile usability and transparent calculations are not optional decorations.&lt;/p&gt;

&lt;p&gt;They are part of building software that respects its users.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How to Measure Whether You Are Actually Improving as a Developer&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It is easy to confuse productivity with progress.&lt;/p&gt;

&lt;p&gt;You might complete ten AI-assisted projects in a month without becoming significantly better at debugging or understanding application architecture.&lt;/p&gt;

&lt;p&gt;Equally, you might spend several weeks improving one application and gain substantial technical knowledge.&lt;/p&gt;

&lt;p&gt;A better approach is to measure both output and understanding.&lt;/p&gt;

&lt;p&gt;Here are five questions worth asking yourself after completing a project.&lt;/p&gt;

&lt;p&gt;Can I explain the important parts of the application?&lt;/p&gt;

&lt;p&gt;You should be able to describe its main functions, how information moves through it and where its important decisions happen.&lt;/p&gt;

&lt;p&gt;Can I identify where something is likely to break?&lt;/p&gt;

&lt;p&gt;You do not need to predict every bug, but you should understand the application's limitations and potential failure points.&lt;/p&gt;

&lt;p&gt;Can I modify an existing feature?&lt;/p&gt;

&lt;p&gt;Try changing a calculation, adding a validation rule or adjusting an existing function without asking AI to rebuild the entire application.&lt;/p&gt;

&lt;p&gt;Can I verify its results independently?&lt;/p&gt;

&lt;p&gt;For calculations, understand the formula. For other applications, understand the expected behaviour and how to test it.&lt;/p&gt;

&lt;p&gt;Can I explain why I chose a particular solution?&lt;/p&gt;

&lt;p&gt;A capable developer should be able to discuss important implementation decisions, including their advantages and disadvantages.&lt;/p&gt;

&lt;p&gt;You can turn these questions into a simple personal development routine.&lt;/p&gt;

&lt;p&gt;After every project, spend 20–30 minutes reviewing what you learned.&lt;/p&gt;

&lt;p&gt;Write down unfamiliar concepts, mistakes you encountered and things you would implement differently next time.&lt;/p&gt;

&lt;p&gt;This creates a record of actual learning rather than simply counting completed projects.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A 30-Day Challenge: Become a More Independent AI-Assisted Developer&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you already use AI for programming, try this practical challenge.&lt;/p&gt;

&lt;p&gt;The objective is not to stop using AI.&lt;/p&gt;

&lt;p&gt;It is to become more confident working with and without it.&lt;/p&gt;

&lt;p&gt;Week 1: Understand Before Generating&lt;/p&gt;

&lt;p&gt;Choose a small application.&lt;/p&gt;

&lt;p&gt;Before asking AI to build it, write down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Its purpose.&lt;/li&gt;
&lt;li&gt;Its required inputs.&lt;/li&gt;
&lt;li&gt;Its expected outputs.&lt;/li&gt;
&lt;li&gt;Its main calculation or business logic.&lt;/li&gt;
&lt;li&gt;Potential errors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ask AI to review your thinking.&lt;/p&gt;

&lt;p&gt;Compare your original plan with its suggestions.&lt;/p&gt;

&lt;p&gt;Week 2: Learn to Read and Debug&lt;/p&gt;

&lt;p&gt;Choose an existing project.&lt;/p&gt;

&lt;p&gt;Identify five functions and understand what each one does.&lt;/p&gt;

&lt;p&gt;Introduce a small, controlled error in a development copy and try to identify it.&lt;/p&gt;

&lt;p&gt;Ask AI for hints rather than immediately requesting the complete solution.&lt;/p&gt;

&lt;p&gt;Week 3: Test What You Build&lt;/p&gt;

&lt;p&gt;Create a simple application with at least five meaningful test cases.&lt;/p&gt;

&lt;p&gt;Include normal inputs, unusual values and invalid entries.&lt;/p&gt;

&lt;p&gt;Compare the results with independently verified expected outcomes.&lt;/p&gt;

&lt;p&gt;Pay particular attention to edge cases.&lt;/p&gt;

&lt;p&gt;Week 4: Build Something You Can Explain&lt;/p&gt;

&lt;p&gt;Build a small but complete project.&lt;/p&gt;

&lt;p&gt;Use AI for assistance, but make sure you understand its important components.&lt;/p&gt;

&lt;p&gt;At the end, try explaining the project to someone who has never seen the code.&lt;/p&gt;

&lt;p&gt;Explain:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What problem it solves.&lt;/li&gt;
&lt;li&gt;How it works.&lt;/li&gt;
&lt;li&gt;What technologies it uses.&lt;/li&gt;
&lt;li&gt;How its important calculations or operations are performed.&lt;/li&gt;
&lt;li&gt;What limitations it has.&lt;/li&gt;
&lt;li&gt;What you would improve next.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you can explain these things clearly, you have achieved something more valuable than simply generating another application.&lt;/p&gt;

&lt;p&gt;You have developed a better understanding of the software you created.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Future Belongs to Developers Who Can Think, Not Just Generate&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It would be a mistake to dismiss artificial intelligence as a temporary development trend.&lt;/p&gt;

&lt;p&gt;AI-assisted development is already changing how software is created, maintained and learned.&lt;/p&gt;

&lt;p&gt;Developers who understand how to use these tools effectively can reduce repetitive work, explore unfamiliar technologies and experiment with ideas that might previously have required considerably more time.&lt;/p&gt;

&lt;p&gt;But the ability to generate code is not the same as the ability to engineer reliable software.&lt;/p&gt;

&lt;p&gt;As code generation becomes easier, other abilities become increasingly important:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understanding requirements.&lt;/li&gt;
&lt;li&gt;Evaluating technical decisions.&lt;/li&gt;
&lt;li&gt;Recognising incorrect implementations.&lt;/li&gt;
&lt;li&gt;Designing reliable systems.&lt;/li&gt;
&lt;li&gt;Testing complex behaviour.&lt;/li&gt;
&lt;li&gt;Protecting user information.&lt;/li&gt;
&lt;li&gt;Understanding real-world problems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are the skills that help turn generated code into dependable products.&lt;/p&gt;

&lt;p&gt;You should not feel guilty about using AI to write code.&lt;/p&gt;

&lt;p&gt;You should also not feel that doing everything manually makes you a better developer.&lt;/p&gt;

&lt;p&gt;The goal is to become someone who can use the available tools intelligently, understand their limitations and take responsibility for the software being produced.&lt;/p&gt;

&lt;p&gt;AI should increase what you can accomplish without reducing your ability to think independently.&lt;/p&gt;

&lt;p&gt;That is a much more valuable definition of productivity.&lt;/p&gt;

&lt;p&gt;Final Thoughts&lt;/p&gt;

&lt;p&gt;The most important question is not whether AI can build an application.&lt;/p&gt;

&lt;p&gt;It clearly can assist with that process.&lt;/p&gt;

&lt;p&gt;The more important question is whether the person directing the process understands what is being built, why it is being built and whether it actually works.&lt;/p&gt;

&lt;p&gt;Technology will continue changing. Programming languages will evolve, development environments will improve and AI assistants will become increasingly capable.&lt;/p&gt;

&lt;p&gt;But the fundamental purpose of software development will remain the same: identifying problems and creating reliable solutions.&lt;/p&gt;

&lt;p&gt;Learn the fundamentals. Build small projects. Experiment. Make mistakes. Read code. Test your assumptions. Use AI when it helps, but never surrender your responsibility to understand what you create.&lt;/p&gt;

&lt;p&gt;Do not measure your growth only by how much code you can generate. Measure it by how much you can understand, improve and build independently.&lt;/p&gt;




&lt;p&gt;A small resource for developers and learners&lt;/p&gt;

&lt;p&gt;One of the reasons I believe useful software matters is that people should not have to struggle with calculations and everyday academic or productivity tasks when a well-designed tool can make them easier.&lt;/p&gt;

&lt;p&gt;That idea is part of what drives AfriWidget, a collection of practical tools covering education, productivity, finance and other everyday needs.&lt;/p&gt;

&lt;p&gt;If you are interested in exploring practical calculators and tools, you can visit "AfriWidget" (&lt;a href="https://afriwidget.com" rel="noopener noreferrer"&gt;https://afriwidget.com&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The larger idea is simple: technology should make difficult things easier to understand and accomplish.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>What Building 120+ Free Web Tools Taught Me About Product Development, SEO, and User Experience</title>
      <dc:creator>Obed Avorlenu</dc:creator>
      <pubDate>Wed, 30 Sep 2026 13:10:38 +0000</pubDate>
      <link>https://dev.to/obed_avorlenu/what-building-120-free-web-tools-taught-me-about-product-development-seo-and-user-experience-49en</link>
      <guid>https://dev.to/obed_avorlenu/what-building-120-free-web-tools-taught-me-about-product-development-seo-and-user-experience-49en</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpsd0v4spr661bas3hq2p.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpsd0v4spr661bas3hq2p.jpg" alt=" " width="250" height="132"&gt;&lt;/a&gt;&lt;br&gt;
When most developers start a new project, they aim to build one product.&lt;/p&gt;

&lt;p&gt;I took a different path.&lt;/p&gt;

&lt;p&gt;Over the past few weeks, I built &lt;a href="https://AfriWidget.com/" rel="noopener noreferrer"&gt;AfriWidget&lt;/a&gt;, a growing collection of free browser-based tools designed to help students, professionals, job seekers, entrepreneurs, and everyday internet users solve practical problems quickly.&lt;/p&gt;

&lt;p&gt;Today, AfriWidget contains more than 120 tools and pages across education, finance, health, productivity, and business.&lt;/p&gt;

&lt;p&gt;The surprising part isn't how many tools were built.&lt;/p&gt;

&lt;p&gt;The surprising part is how much I learned from building them.&lt;/p&gt;

&lt;p&gt;Creating dozens of tools forced me to think differently about product development, user behavior, search intent, retention, performance, and what actually creates value online.&lt;/p&gt;

&lt;p&gt;This article shares some of the most important lessons from that journey.&lt;/p&gt;

&lt;p&gt;The Internet Still Rewards Useful Tools&lt;/p&gt;

&lt;p&gt;Many discussions in tech today revolve around AI, startups, SaaS, and automation.&lt;/p&gt;

&lt;p&gt;While those topics are important, there's another opportunity that often gets overlooked:&lt;/p&gt;

&lt;p&gt;Solving small, everyday problems.&lt;/p&gt;

&lt;p&gt;People search the internet for highly specific needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How do I calculate my WASSCE aggregate?&lt;/li&gt;
&lt;li&gt;Am I eligible for nursing training admission?&lt;/li&gt;
&lt;li&gt;What GPA do I need to graduate?&lt;/li&gt;
&lt;li&gt;How much will my startup cost?&lt;/li&gt;
&lt;li&gt;How can I create a robots.txt file?&lt;/li&gt;
&lt;li&gt;How do I calculate my debt repayment timeline?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not glamorous problems.&lt;/p&gt;

&lt;p&gt;They won't make headlines on technology blogs.&lt;/p&gt;

&lt;p&gt;But they matter to the people searching for answers.&lt;/p&gt;

&lt;p&gt;One thing I quickly discovered while building AfriWidget is that usefulness beats complexity surprisingly often.&lt;/p&gt;

&lt;p&gt;A tool that saves someone five minutes today may provide more value than a feature-packed application they never fully use.&lt;/p&gt;

&lt;p&gt;Why AfriWidget Focuses on Practical Solutions&lt;/p&gt;

&lt;p&gt;When planning the platform, I deliberately avoided creating tools just because they sounded impressive.&lt;/p&gt;

&lt;p&gt;Instead, I focused on questions real people were already asking.&lt;/p&gt;

&lt;p&gt;For example, educational calculators became one of the strongest categories on the platform.&lt;/p&gt;

&lt;p&gt;Students frequently need help calculating:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WASSCE aggregates&lt;/li&gt;
&lt;li&gt;BECE aggregates&lt;/li&gt;
&lt;li&gt;JAMB UTME scores&lt;/li&gt;
&lt;li&gt;GPA and CGPA&lt;/li&gt;
&lt;li&gt;APS scores&lt;/li&gt;
&lt;li&gt;Admission eligibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many existing solutions were either outdated, confusing, filled with intrusive advertisements, or required unnecessary registration.&lt;/p&gt;

&lt;p&gt;The goal became simple:&lt;/p&gt;

&lt;p&gt;Create tools that solve the problem immediately.&lt;/p&gt;

&lt;p&gt;No account creation.&lt;/p&gt;

&lt;p&gt;No downloads.&lt;/p&gt;

&lt;p&gt;No complicated onboarding.&lt;/p&gt;

&lt;p&gt;Open the page, enter the information, and get the result.&lt;/p&gt;

&lt;p&gt;That philosophy now guides every new feature added to AfriWidget.&lt;/p&gt;

&lt;p&gt;Building More Than 120 Tools Revealed a Pattern&lt;/p&gt;

&lt;p&gt;One lesson became clear very quickly.&lt;/p&gt;

&lt;p&gt;Users don't care how difficult something was to build.&lt;/p&gt;

&lt;p&gt;They care whether it solves their problem.&lt;/p&gt;

&lt;p&gt;Developers sometimes spend weeks implementing sophisticated technology while overlooking the actual user experience.&lt;/p&gt;

&lt;p&gt;Meanwhile, a simple calculator built in a few hours can become one of the most useful parts of a platform.&lt;/p&gt;

&lt;p&gt;This changed how I evaluate ideas.&lt;/p&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;p&gt;«"How technically impressive is this?"»&lt;/p&gt;

&lt;p&gt;I started asking:&lt;/p&gt;

&lt;p&gt;«"How much friction does this remove from someone's life?"»&lt;/p&gt;

&lt;p&gt;That question leads to better products.&lt;/p&gt;

&lt;p&gt;Why I Chose Simplicity Over Complexity&lt;/p&gt;

&lt;p&gt;Many modern websites rely heavily on large frameworks and extensive client-side processing.&lt;/p&gt;

&lt;p&gt;There's nothing wrong with that approach when it's necessary.&lt;/p&gt;

&lt;p&gt;However, most utility tools don't need massive JavaScript bundles or complex infrastructure.&lt;/p&gt;

&lt;p&gt;For AfriWidget, performance became a priority from the beginning.&lt;/p&gt;

&lt;p&gt;The platform focuses on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast page loads&lt;/li&gt;
&lt;li&gt;Mobile responsiveness&lt;/li&gt;
&lt;li&gt;Simple navigation&lt;/li&gt;
&lt;li&gt;Browser-based processing&lt;/li&gt;
&lt;li&gt;Minimal waiting time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach creates several benefits.&lt;/p&gt;

&lt;p&gt;Faster User Experience&lt;/p&gt;

&lt;p&gt;People can access tools immediately without waiting for heavy applications to load.&lt;/p&gt;

&lt;p&gt;Better Accessibility&lt;/p&gt;

&lt;p&gt;Users with slower internet connections can still use the platform comfortably.&lt;/p&gt;

&lt;p&gt;Easier Maintenance&lt;/p&gt;

&lt;p&gt;Simple systems are generally easier to improve and troubleshoot.&lt;/p&gt;

&lt;p&gt;Stronger SEO Foundations&lt;/p&gt;

&lt;p&gt;Search engines can easily understand content when pages are structured clearly and load efficiently.&lt;/p&gt;

&lt;p&gt;Sometimes the best optimization isn't adding more technology.&lt;/p&gt;

&lt;p&gt;It's removing unnecessary complexity.&lt;/p&gt;

&lt;p&gt;The Most Important SEO Lesson I Learned&lt;/p&gt;

&lt;p&gt;Before building AfriWidget, I thought SEO success primarily depended on keywords.&lt;/p&gt;

&lt;p&gt;Keywords matter.&lt;/p&gt;

&lt;p&gt;But they are only one part of the picture.&lt;/p&gt;

&lt;p&gt;The bigger lesson was understanding search intent.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;A person searching for "WASSCE aggregate calculator" doesn't want a lengthy article explaining education systems.&lt;/p&gt;

&lt;p&gt;They want a calculator.&lt;/p&gt;

&lt;p&gt;Immediately.&lt;/p&gt;

&lt;p&gt;The content should support the tool, not distract from it.&lt;/p&gt;

&lt;p&gt;On the other hand, someone searching for admission requirements may benefit from educational content, explanations, examples, and frequently asked questions.&lt;/p&gt;

&lt;p&gt;Understanding what the user actually wants is often more important than chasing high-volume keywords.&lt;/p&gt;

&lt;p&gt;The pages that performed best were usually the ones that solved a specific problem clearly and efficiently.&lt;/p&gt;

&lt;p&gt;Why Retention Matters for Tool Websites&lt;/p&gt;

&lt;p&gt;Tool websites face a unique challenge.&lt;/p&gt;

&lt;p&gt;Most users arrive, complete a task, and leave.&lt;/p&gt;

&lt;p&gt;That behavior isn't necessarily bad.&lt;/p&gt;

&lt;p&gt;The tool did its job.&lt;/p&gt;

&lt;p&gt;However, I wanted AfriWidget to become more than a collection of one-time-use pages.&lt;/p&gt;

&lt;p&gt;That led to the development of several retention-focused features.&lt;/p&gt;

&lt;p&gt;Save Favorite Tools&lt;/p&gt;

&lt;p&gt;Users can save tools they use regularly.&lt;/p&gt;

&lt;p&gt;This makes it easier to return to frequently used resources.&lt;/p&gt;

&lt;p&gt;Continue Where You Left Off&lt;/p&gt;

&lt;p&gt;Progress is automatically stored locally.&lt;/p&gt;

&lt;p&gt;When users revisit a tool, they can continue without starting over.&lt;/p&gt;

&lt;p&gt;Saved Inputs&lt;/p&gt;

&lt;p&gt;For calculators and educational tools, repeating the same information can be frustrating.&lt;/p&gt;

&lt;p&gt;Allowing data to persist improves convenience significantly.&lt;/p&gt;

&lt;p&gt;These features don't generate flashy marketing headlines.&lt;/p&gt;

&lt;p&gt;But they improve real user experience.&lt;/p&gt;

&lt;p&gt;And user experience often determines whether someone returns.&lt;/p&gt;

&lt;p&gt;What I Learned About AI Features&lt;/p&gt;

&lt;p&gt;Like many developers, I experimented extensively with AI-powered functionality.&lt;/p&gt;

&lt;p&gt;Some features provided genuine value.&lt;/p&gt;

&lt;p&gt;Others sounded impressive but didn't significantly improve outcomes.&lt;/p&gt;

&lt;p&gt;This led to an important realization:&lt;/p&gt;

&lt;p&gt;Users care more about results than technology.&lt;/p&gt;

&lt;p&gt;Most people don't visit a tool because it uses AI.&lt;/p&gt;

&lt;p&gt;They visit because they need help solving a problem.&lt;/p&gt;

&lt;p&gt;If AI improves the experience, great.&lt;/p&gt;

&lt;p&gt;If it doesn't, users won't care that it exists.&lt;/p&gt;

&lt;p&gt;This perspective helped prioritize features based on usefulness rather than trends.&lt;/p&gt;

&lt;p&gt;The Five Core Areas of AfriWidget&lt;/p&gt;

&lt;p&gt;Today, AfriWidget focuses on five major categories.&lt;/p&gt;

&lt;p&gt;Education&lt;/p&gt;

&lt;p&gt;Educational tools remain one of the platform's strongest areas.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WASSCE calculators&lt;/li&gt;
&lt;li&gt;BECE calculators&lt;/li&gt;
&lt;li&gt;JAMB calculators&lt;/li&gt;
&lt;li&gt;GPA calculators&lt;/li&gt;
&lt;li&gt;APS calculators&lt;/li&gt;
&lt;li&gt;Nursing admission tools&lt;/li&gt;
&lt;li&gt;University eligibility checkers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These tools help students make informed academic decisions quickly.&lt;/p&gt;

&lt;p&gt;Finance&lt;/p&gt;

&lt;p&gt;Financial planning can be overwhelming.&lt;/p&gt;

&lt;p&gt;The finance section includes tools that help users understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Debt repayment&lt;/li&gt;
&lt;li&gt;Budget planning&lt;/li&gt;
&lt;li&gt;Financial calculations&lt;/li&gt;
&lt;li&gt;Savings projections&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Business&lt;/p&gt;

&lt;p&gt;Business tools focus on helping entrepreneurs and freelancers estimate costs, evaluate ideas, and plan projects.&lt;/p&gt;

&lt;p&gt;Productivity&lt;/p&gt;

&lt;p&gt;Productivity tools aim to eliminate repetitive tasks and improve efficiency.&lt;/p&gt;

&lt;p&gt;Health&lt;/p&gt;

&lt;p&gt;Health-related calculators provide quick insights that users can access directly within their browsers.&lt;/p&gt;

&lt;p&gt;Together, these categories create a platform designed around practical usefulness rather than entertainment.&lt;/p&gt;

&lt;p&gt;Building in Public Accelerates Learning&lt;/p&gt;

&lt;p&gt;One unexpected benefit of building AfriWidget was how quickly users provided feedback.&lt;/p&gt;

&lt;p&gt;People reported issues.&lt;/p&gt;

&lt;p&gt;Suggested improvements.&lt;/p&gt;

&lt;p&gt;Requested features.&lt;/p&gt;

&lt;p&gt;Shared frustrations.&lt;/p&gt;

&lt;p&gt;Every interaction revealed something new.&lt;/p&gt;

&lt;p&gt;Building publicly creates a continuous feedback loop.&lt;/p&gt;

&lt;p&gt;Instead of guessing what users want, developers can observe actual behavior and adapt accordingly.&lt;/p&gt;

&lt;p&gt;Many of the platform's improvements came directly from watching how people interacted with existing tools.&lt;/p&gt;

&lt;p&gt;The Future of Utility Websites&lt;/p&gt;

&lt;p&gt;I believe utility websites still have enormous potential.&lt;/p&gt;

&lt;p&gt;Despite advances in AI and software development, people continue searching for straightforward solutions.&lt;/p&gt;

&lt;p&gt;The demand for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Calculators&lt;/li&gt;
&lt;li&gt;Checkers&lt;/li&gt;
&lt;li&gt;Converters&lt;/li&gt;
&lt;li&gt;Planning tools&lt;/li&gt;
&lt;li&gt;Educational resources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;is not disappearing.&lt;/p&gt;

&lt;p&gt;If anything, users increasingly appreciate products that are fast, focused, and easy to use.&lt;/p&gt;

&lt;p&gt;The challenge isn't building more features.&lt;/p&gt;

&lt;p&gt;The challenge is building the right features.&lt;/p&gt;

&lt;p&gt;Final Thoughts&lt;/p&gt;

&lt;p&gt;Building AfriWidget taught me that successful products don't always start with groundbreaking innovation.&lt;/p&gt;

&lt;p&gt;Sometimes they start with a simple question:&lt;/p&gt;

&lt;p&gt;What problem can I solve today?&lt;/p&gt;

&lt;p&gt;By focusing on usefulness, speed, accessibility, and user experience, a collection of small tools can become something much larger.&lt;/p&gt;

&lt;p&gt;More than 120 tools later, the biggest lesson remains unchanged:&lt;/p&gt;

&lt;p&gt;Technology is most valuable when it makes life easier.&lt;/p&gt;

&lt;p&gt;Whether you're building a startup, launching a side project, or creating your first web application, remember that solving real problems consistently is often more important than building the most complicated solution.&lt;/p&gt;

&lt;p&gt;That's the philosophy behind AfriWidget, and it's the principle I'll continue following as the platform grows.&lt;/p&gt;

&lt;p&gt;If you're building products yourself, I'd love to hear what lessons you've learned from creating tools that people actually use.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>opensource</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>Building Web Tools That Actually Serve Emerging Markets</title>
      <dc:creator>Obed Avorlenu</dc:creator>
      <pubDate>Sun, 09 Aug 2026 18:35:00 +0000</pubDate>
      <link>https://dev.to/obed_avorlenu/building-web-tools-that-actually-serve-emerging-markets-188c</link>
      <guid>https://dev.to/obed_avorlenu/building-web-tools-that-actually-serve-emerging-markets-188c</guid>
      <description>&lt;p&gt;There's a fundamental question I keep coming back to when building for the web: Who are we actually building for?&lt;/p&gt;

&lt;p&gt;If you're reading this on a 5G connection with a flagship phone, it's easy to forget that most of the world doesn't have that luxury. According to the GSMA, nearly half the global population still isn't using mobile internet, and for those who are, connections are often slow, data is expensive, and devices are older.&lt;/p&gt;

&lt;p&gt;I recently came across &lt;a href="https://AfriWidget.com/" rel="noopener noreferrer"&gt;AfriWidget&lt;/a&gt; — a platform building practical calculators for health, finance, and education — and it got me thinking about what it actually takes to build tools that serve users in emerging markets. Not as a charity case, but as a deliberate design choice.&lt;/p&gt;

&lt;p&gt;Here's what I learned.&lt;/p&gt;




&lt;p&gt;The Problem: One-Size-Fits-None&lt;/p&gt;

&lt;p&gt;Most web tools are built for users in North America, Europe, or wealthy parts of Asia. The assumptions baked into the code are:&lt;/p&gt;

&lt;p&gt;· Fast, reliable internet&lt;br&gt;
· Modern smartphones or laptops&lt;br&gt;
· Disposable income for premium features&lt;br&gt;
· Familiarity with Western academic and financial systems&lt;/p&gt;

&lt;p&gt;But what if your users are in Lagos, Nairobi, or Accra? What if they're preparing for WASSCE or BECE exams instead of the SAT? What if they're managing finances in a country with high inflation and currency volatility?&lt;/p&gt;

&lt;p&gt;AfriWidget was built precisely for this gap. The platform recognizes that millions of people worldwide lack access to personalized financial advisors, nutrition coaches, and academic counselors that wealthier demographics take for granted.&lt;/p&gt;

&lt;p&gt;So how do you build for that reality?&lt;/p&gt;



&lt;p&gt;Lesson 1: Compute Locally, Not in the Cloud&lt;/p&gt;

&lt;p&gt;The most important architectural decision you can make for emerging markets is pushing computation to the client.&lt;/p&gt;

&lt;p&gt;AfriWidget's core principle is simple: most calculations stay in your browser. The BMI calculator, the BMR calculator, the GPA calculator — all of them run locally using JavaScript.&lt;/p&gt;

&lt;p&gt;Why this matters:&lt;/p&gt;

&lt;p&gt;Zero latency. Results appear instantly, no spinner, no "loading..." state.&lt;/p&gt;

&lt;p&gt;Works offline. Once the page loads, the calculator functions without a network connection.&lt;/p&gt;

&lt;p&gt;Saves data. Every round-trip to a server costs your user money. In many African countries, mobile data is expensive relative to income.&lt;/p&gt;

&lt;p&gt;Reduces server costs. You're not paying for compute time on every calculation.&lt;/p&gt;

&lt;p&gt;The technical implementation isn't complicated. It's just vanilla JavaScript doing math. But the impact on user experience is massive.&lt;/p&gt;



&lt;p&gt;Lesson 2: Use Evidence-Based Formulas, Not Vibes&lt;/p&gt;

&lt;p&gt;When you're building tools that people rely on for real decisions — health, finance, academics — you have a responsibility to get the math right.&lt;/p&gt;

&lt;p&gt;AfriWidget's calculators use internationally recognised, clinically and academically validated formulas:&lt;/p&gt;

&lt;p&gt;· BMI: Follows World Health Organisation (WHO) guidelines&lt;br&gt;
· BMR: Uses the Mifflin-St Jeor equation, considered the gold standard&lt;br&gt;
· GPA: Reflects standard 4.0-scale grading conventions&lt;/p&gt;

&lt;p&gt;This isn't just about accuracy. It's about trust. When a user in a region with limited access to healthcare uses your BMI calculator, they need to know the result is reliable.&lt;/p&gt;

&lt;p&gt;For developers: Always cite your sources. Be transparent about limitations. And if you're using AI-generated insights, make sure the underlying model is grounded in real data.&lt;/p&gt;



&lt;p&gt;Lesson 3: Design for Slow Networks and Old Devices&lt;/p&gt;

&lt;p&gt;AfriWidget is built for users across Africa and the developing world. The tools work on low-bandwidth connections and older devices.&lt;/p&gt;

&lt;p&gt;What does that mean in practice?&lt;/p&gt;

&lt;p&gt;· No heavy frameworks. AfriWidget uses static HTML with minimal JavaScript payloads.&lt;br&gt;
· No app store required. It's a web app, accessible from any browser.&lt;br&gt;
· No premium tier. No paywalls. Just knowledge, freely accessible.&lt;/p&gt;

&lt;p&gt;This is a reminder that "modern" doesn't always mean "better." If your React bundle is 2MB, you've already lost a significant portion of potential users in emerging markets.&lt;/p&gt;



&lt;p&gt;Lesson 4: AI Is Possible — With a Secure Proxy&lt;/p&gt;

&lt;p&gt;One of the more interesting aspects of AfriWidget is how they handle AI-powered insights. Finance and Education tools may send limited calculation context through a secure server-side Groq proxy for an AI explanation.&lt;/p&gt;

&lt;p&gt;The architecture looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser calculates result → Sends only context (e.g., "principal: 1000, rate: 5%")
→ Proxy attaches API key → Calls Groq → Returns insight
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key design decisions:&lt;/p&gt;

&lt;p&gt;· The proxy keeps the Groq key out of the browser&lt;br&gt;
· No personal identifiers are sent&lt;br&gt;
· No database of calculator submissions is maintained&lt;/p&gt;

&lt;p&gt;This is the secure proxy pattern I covered in a previous article. It's a solid way to add AI features without compromising user privacy or exposing API keys.&lt;/p&gt;



&lt;p&gt;Lesson 5: Privacy Isn't Optional — It's a Feature&lt;/p&gt;

&lt;p&gt;AfriWidget has no user accounts and does not use calculator inputs for advertising targeting.&lt;/p&gt;

&lt;p&gt;Think about what that means:&lt;/p&gt;

&lt;p&gt;· No password hashes to steal&lt;br&gt;
· No personal data to expose in a breach&lt;br&gt;
· No GDPR compliance headaches for user data&lt;br&gt;
· No "forgot password" flows to build and maintain&lt;/p&gt;

&lt;p&gt;Every piece of data you don't collect is a vulnerability you don't have to defend.&lt;/p&gt;

&lt;p&gt;For a platform serving users in regions where digital privacy concerns are growing, this is a massive trust signal. Users can calculate their BMI, plan their savings, or check their GPA without worrying about who's watching.&lt;/p&gt;



&lt;p&gt;What This Means for Developers&lt;/p&gt;

&lt;p&gt;If you're building web tools for a global audience, here are the takeaways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Question your assumptions. Who are you actually building for? What's their internet speed? What device are they using?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Push computation to the client. Modern browsers are incredibly capable. Use them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Validate your formulas. If you're building a health or finance tool, use established standards. Cite your sources.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Keep it lightweight. Every kilobyte matters. Every network request costs your user money.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Design for privacy from day one. Don't collect what you don't need. Don't store what you don't have to.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you use AI, use a proxy. Keep API keys secure. Send only the minimum context needed.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;



&lt;p&gt;Further Reading&lt;/p&gt;

&lt;p&gt;· AfriWidget — Free Health, Finance &amp;amp; Education Tools&lt;br&gt;
· AfriWidget Privacy Promise — client-side computation, no accounts, secure AI proxy&lt;br&gt;
· WHO BMI Guidelines&lt;br&gt;
· Mifflin-St Jeor Equation — the gold standard for BMR estimation&lt;/p&gt;



&lt;p&gt;Final Thoughts&lt;/p&gt;

&lt;p&gt;Building for emerging markets isn't about charity. It's about good engineering. It forces you to be efficient, privacy-conscious, and user-focused in ways that building for the "ideal" user never does.&lt;/p&gt;

&lt;p&gt;AfriWidget demonstrates that you can build sophisticated, AI-enhanced tools while keeping the user experience fast, the data private, and the code lightweight. The patterns are straightforward: compute locally, proxy securely, collect nothing you don't need, and validate everything you do.&lt;/p&gt;

&lt;p&gt;The next time you're building a web tool, ask yourself: Would this work on a 3G connection in rural Ghana? If the answer is no, you might have some work to do.&lt;/p&gt;



&lt;p&gt;What strategies have you used to build for users in emerging markets? Drop a comment below — I'd love to hear your approach.&lt;/p&gt;



&lt;p&gt;Tags&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#webdev, #javascript, #privacy, #accessibility, #programming
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>privacy</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Privacy in Plain Sight: What 3 Calculator Platforms Tell Us About Data Collection</title>
      <dc:creator>Obed Avorlenu</dc:creator>
      <pubDate>Sun, 09 Aug 2026 18:29:47 +0000</pubDate>
      <link>https://dev.to/obed_avorlenu/privacy-in-plain-sight-what-3-calculator-platforms-tell-us-about-data-collection-4aob</link>
      <guid>https://dev.to/obed_avorlenu/privacy-in-plain-sight-what-3-calculator-platforms-tell-us-about-data-collection-4aob</guid>
      <description>&lt;p&gt;Online calculators are everywhere. Need to check your BMI? Calculate a mortgage payment? Figure out your GPA? There's a free tool for that.&lt;/p&gt;

&lt;p&gt;But here's the uncomfortable question: What happens to the numbers you type in?&lt;/p&gt;

&lt;p&gt;Most of us assume these tools are private. We're just doing math, right? But behind the scenes, the data you enter—your weight, your salary, your savings goals—can be valuable. And different platforms handle that data very differently.&lt;/p&gt;

&lt;p&gt;I spent some time digging into the privacy practices of three popular calculator platforms: Calculator.net, OmniCalculator, and AfriWidget. Here's what I found, and what it means for developers building similar tools.&lt;/p&gt;




&lt;p&gt;The Players&lt;/p&gt;

&lt;p&gt;Calculator.net (launched 2007, 200+ calculators) — claims to take privacy seriously but offers vague policy language. OmniCalculator (launched ~2014, 1000+ calculators) — explicitly states they don't store or sell calculator inputs. AfriWidget (launched 2024, 84 tools) — markets itself as privacy-first with client-side computation by default.&lt;/p&gt;

&lt;p&gt;All three claim to respect user privacy. But the details tell a different story.&lt;/p&gt;




&lt;p&gt;Calculator.net: The Established Incumbent&lt;/p&gt;

&lt;p&gt;Calculator.net has been around since 2007. It's the granddaddy of online calculators, with over 200 tools covering finance, health, math, and more.&lt;/p&gt;

&lt;p&gt;Privacy Practices:&lt;/p&gt;

&lt;p&gt;· Collects "Log Data" including browser information and IP addresses&lt;br&gt;
· Uses cookies and weblogs to "customize services and enhance customer satisfaction"&lt;br&gt;
· For registered users, they store registration information and saved calculations&lt;br&gt;
· Registered users can delete their data, but it requires a request and two business days to process&lt;/p&gt;

&lt;p&gt;The Bottom Line: Calculator.net collects standard analytics data and offers registration for users who want to save calculations. The privacy policy is vague about what "Log Data" actually includes and how long it's retained.&lt;/p&gt;

&lt;p&gt;Third-Party Tracking: An analysis found 114 third-party tracking requests on calculator.net, along with 5 cookies. That's a lot of potential data flowing to advertisers and analytics providers.&lt;/p&gt;



&lt;p&gt;OmniCalculator: The Polish Upstart&lt;/p&gt;

&lt;p&gt;OmniCalculator, founded in Poland, has grown rapidly with over 1,000 calculators across dozens of categories.&lt;/p&gt;

&lt;p&gt;Privacy Practices:&lt;/p&gt;

&lt;p&gt;· Explicitly states: "We neither store nor sell the data you enter into our calculators"&lt;br&gt;
· Collects personal information only when users voluntarily provide it (newsletter signups, job applications, contact forms)&lt;br&gt;
· No account registration required to use calculators&lt;br&gt;
· Offers standard GDPR rights: access, rectification, erasure, restriction, portability&lt;/p&gt;

&lt;p&gt;The Bottom Line: OmniCalculator has a clean, transparent privacy policy that clearly separates calculator inputs from personal data. They don't store what you calculate.&lt;/p&gt;

&lt;p&gt;Third-Party Tracking: The site uses cookies and has some third-party tracking (analytics.tiktok.com, Google Analytics), but their policy is more explicit than Calculator.net's.&lt;/p&gt;



&lt;p&gt;AfriWidget: The Privacy-First Newcomer&lt;/p&gt;

&lt;p&gt;AfriWidget is a newer entrant focused on serving users in Africa and developing regions, with 84 tools across health, finance, and education.&lt;/p&gt;

&lt;p&gt;Privacy Practices:&lt;/p&gt;

&lt;p&gt;· "Most calculations stay in your browser" — client-side computation by default&lt;br&gt;
· No user accounts — no registration, no stored data&lt;br&gt;
· No calculator-submission database — they don't keep what you calculate&lt;br&gt;
· Calculator inputs are not used for advertising targeting&lt;br&gt;
· Finance and Education tools may send limited calculation context through a secure server-side Groq proxy for AI insights&lt;br&gt;
· The proxy keeps the API key out of the browser and does not receive personal identifiers&lt;/p&gt;

&lt;p&gt;The Bottom Line: AfriWidget takes a more aggressive privacy stance. They don't collect anything by default. Even their AI features are designed with privacy in mind—only calculation context (numbers, not names) is sent to the proxy.&lt;/p&gt;

&lt;p&gt;Third-Party Tracking: The site is supported by "non-intrusive advertising", but calculator inputs are explicitly not used for ad targeting.&lt;/p&gt;



&lt;p&gt;The Technical Comparison&lt;/p&gt;

&lt;p&gt;Let's look under the hood at how each platform handles your data.&lt;/p&gt;

&lt;p&gt;Architecture Approaches&lt;/p&gt;

&lt;p&gt;Calculator.net:&lt;/p&gt;

&lt;p&gt;· Mixed approach&lt;br&gt;
· Some calculations run client-side, but they also offer user accounts and saved calculations (requires server-side storage)&lt;br&gt;
· Privacy policy suggests they collect usage data via logs and cookies&lt;/p&gt;

&lt;p&gt;OmniCalculator:&lt;/p&gt;

&lt;p&gt;· Mostly client-side&lt;br&gt;
· Claims calculations happen in the browser&lt;br&gt;
· Collects analytics data and uses cookies&lt;br&gt;
· Policy is clear that calculator inputs aren't stored (but IP addresses and device info are collected)&lt;/p&gt;

&lt;p&gt;AfriWidget:&lt;/p&gt;

&lt;p&gt;· Client-first with a secure proxy for AI&lt;br&gt;
· Default is local computation&lt;br&gt;
· When AI insights are requested, only the minimal calculation context goes to the server&lt;br&gt;
· No names, no personal identifiers&lt;br&gt;
· No database of submissions is maintained&lt;/p&gt;

&lt;p&gt;The Secure Proxy Pattern&lt;/p&gt;

&lt;p&gt;AfriWidget's approach to AI is worth examining. Instead of calling Groq (or any LLM provider) directly from the browser—which would expose the API key—they use a server-side proxy.&lt;/p&gt;

&lt;p&gt;The flow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser calculates result → Sends only context (e.g., "principal: 1000, rate: 5%")
→ Proxy attaches API key → Calls Groq → Returns insight → Nothing is logged or stored
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the same pattern I covered in my previous article on building secure AI proxies. It's a solid architectural choice that balances AI features with privacy.&lt;/p&gt;




&lt;p&gt;What This Means for Developers&lt;/p&gt;

&lt;p&gt;If you're building a calculator or any tool that handles user data, here are the key takeaways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Be Explicit About What You Collect&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Calculator.net's privacy policy is vague. OmniCalculator's is clearer. AfriWidget's is the most explicit. Users deserve to know exactly what happens to their data.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Client-Side Computation Is Your Friend&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The more you can run in the browser, the less data you have to collect, store, or protect. AfriWidget's "most calculations stay in your browser" approach is the gold standard.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If You Need AI, Use a Proxy&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Don't expose API keys in client-side code. Use a server-side proxy that receives only the minimum necessary context. AfriWidget's Groq proxy is a good example of this pattern.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;No Accounts = No Data to Leak&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;User accounts create data storage requirements and security risks. AfriWidget has no user accounts, which means no password hashes to steal, no personal data to expose, and no GDPR compliance headaches for user data.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Third-Party Tracking Adds Up&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Calculator.net's 114 third-party tracking requests is a lot. Every tracking pixel, analytics script, and ad network is another potential vector for data leakage. Be intentional about what you include.&lt;/p&gt;




&lt;p&gt;The Privacy Spectrum&lt;/p&gt;

&lt;p&gt;These three platforms represent different points on the privacy spectrum. Here's how they compare:&lt;/p&gt;

&lt;p&gt;Client-side computation:&lt;/p&gt;

&lt;p&gt;· Calculator.net: Partial&lt;br&gt;
· OmniCalculator: Mostly&lt;br&gt;
· &lt;a href="https://AfriWidget.com/" rel="noopener noreferrer"&gt;AfriWidget&lt;/a&gt;: Default&lt;/p&gt;

&lt;p&gt;User accounts:&lt;/p&gt;

&lt;p&gt;· Calculator.net: Yes&lt;br&gt;
· OmniCalculator: No&lt;br&gt;
· &lt;a href="https://AfriWidget.com/" rel="noopener noreferrer"&gt;AfriWidget&lt;/a&gt;: No&lt;/p&gt;

&lt;p&gt;Stores calculator inputs:&lt;/p&gt;

&lt;p&gt;· Calculator.net: Yes (for registered users)&lt;br&gt;
· OmniCalculator: No&lt;br&gt;
· AfriWidget: No&lt;/p&gt;

&lt;p&gt;AI features:&lt;/p&gt;

&lt;p&gt;· Calculator.net: No&lt;br&gt;
· OmniCalculator: No&lt;br&gt;
· &lt;a href="https://AfriWidget.com/" rel="noopener noreferrer"&gt;AfriWidget&lt;/a&gt;: Yes (via secure proxy)&lt;/p&gt;

&lt;p&gt;Third-party tracking:&lt;/p&gt;

&lt;p&gt;· Calculator.net: Heavy (114 requests)&lt;br&gt;
· OmniCalculator: Moderate&lt;br&gt;
· AfriWidget: Minimal&lt;/p&gt;

&lt;p&gt;Privacy policy clarity:&lt;/p&gt;

&lt;p&gt;· Calculator.net: Vague&lt;br&gt;
· OmniCalculator: Clear&lt;br&gt;
· AfriWidget: Very clear&lt;/p&gt;




&lt;p&gt;Final Thoughts&lt;/p&gt;

&lt;p&gt;Privacy isn't binary. It's a spectrum of choices about what data you collect, how you store it, and what you do with it.&lt;/p&gt;

&lt;p&gt;Calculator.net, as the oldest platform, has accumulated the most data-collection baggage. OmniCalculator takes a cleaner approach but still collects analytics. AfriWidget, as the newest entrant, has designed privacy in from day one.&lt;/p&gt;

&lt;p&gt;For developers, the lesson is clear: Privacy is a design choice, not an afterthought. The decisions you make about architecture—client-side vs. server-side, accounts vs. no accounts, analytics vs. no analytics—have real implications for your users.&lt;/p&gt;

&lt;p&gt;And if you're building a tool that handles sensitive data (health metrics, financial information, academic records), the privacy-first approach isn't just nice to have. It's the only responsible choice.&lt;/p&gt;




&lt;p&gt;Further Reading&lt;/p&gt;

&lt;p&gt;· AfriWidget - Privacy-First Calculators&lt;br&gt;
· Calculator.net Privacy Policy&lt;br&gt;
· OmniCalculator Privacy Policy&lt;/p&gt;




&lt;p&gt;What's your approach to privacy in the tools you build? Drop a comment below—I'd love to hear how you handle user data.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>privacy</category>
      <category>programming</category>
    </item>
    <item>
      <title>Topic selected: Option A – Purely Technical: "Building a Secure AI Proxy for Browser Tools</title>
      <dc:creator>Obed Avorlenu</dc:creator>
      <pubDate>Sun, 09 Aug 2026 18:20:32 +0000</pubDate>
      <link>https://dev.to/obed_avorlenu/topic-selected-option-a-purely-technical-building-a-secure-ai-proxy-for-browser-tools-5bc</link>
      <guid>https://dev.to/obed_avorlenu/topic-selected-option-a-purely-technical-building-a-secure-ai-proxy-for-browser-tools-5bc</guid>
      <description>&lt;p&gt;This is the strongest choice. It teaches a tangible, highly demanded skill (API key security) with actual code, making the backlink to AfriWidget feel like a natural, neutral citation rather than a sales pitch.&lt;/p&gt;

&lt;p&gt;Here is the article, rewritten to be strictly technical, objective, and genuinely useful for dev.to readers.&lt;/p&gt;




&lt;p&gt;Stop Exposing Your AI API Keys: Build a Secure Proxy with Cloudflare Workers&lt;/p&gt;

&lt;p&gt;We have all seen it. You open the browser's DevTools on a "cutting-edge" AI startup's landing page, check the Network tab, and find a direct POST request to api.openai.com containing a plaintext API key in the headers.&lt;/p&gt;

&lt;p&gt;It is one of the most common—and dangerous—mistakes in modern web development. Exposing your LLM API key client-side is an open invitation for abuse, leading to stolen credits, hefty bills, and potential account suspension.&lt;/p&gt;

&lt;p&gt;The standard solution is the Backend-for-Frontend (BFF) proxy pattern. But how do you implement it practically, cheaply, and securely without spinning up a heavy Express server?&lt;/p&gt;

&lt;p&gt;In this guide, I will walk you through building a lightweight, serverless AI proxy using Cloudflare Workers to securely call Groq (or OpenAI) APIs from your browser-based calculators and tools.&lt;/p&gt;




&lt;p&gt;The Architecture: How It Works&lt;/p&gt;

&lt;p&gt;Instead of your frontend talking directly to the AI provider, we introduce a stateless middleware layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser App → Cloudflare Worker (Proxy) → Groq/OpenAI API
     ↑                ↑
  (No API Key)   (API Key stored securely in Worker env vars)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Worker's responsibilities:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Receive the sanitized calculation context from the frontend (numbers, not PII).&lt;/li&gt;
&lt;li&gt;Attach the secret API key via environment variables.&lt;/li&gt;
&lt;li&gt;Forward the request to the LLM provider.&lt;/li&gt;
&lt;li&gt;Stream or return the generated insight back to the client.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;Step 1: Scaffolding the Cloudflare Worker&lt;/p&gt;

&lt;p&gt;We will use the new create-cloudflare CLI. Make sure you have Node.js installed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create cloudflare@latest ai-proxy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Choose "Hello World" worker and TypeScript. Once inside the directory, install the Groq SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;groq-sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Step 2: Securing the API Key&lt;/p&gt;

&lt;p&gt;Never hardcode keys. Cloudflare Workers expose environment variables securely.&lt;/p&gt;

&lt;p&gt;Update your wrangler.toml:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ai-proxy"&lt;/span&gt;
&lt;span class="py"&gt;main&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"src/index.ts"&lt;/span&gt;
&lt;span class="py"&gt;compatibility_date&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2024-12-18"&lt;/span&gt;

&lt;span class="nn"&gt;[vars]&lt;/span&gt;
&lt;span class="py"&gt;GROQ_API_KEY&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"your-secret-key-here"&lt;/span&gt; &lt;span class="c"&gt;# Replace, but prefer using wrangler secret for production&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For production, set it as an actual secret to hide it from the dashboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx wrangler secret put GROQ_API_KEY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Step 3: Writing the Worker Logic&lt;/p&gt;

&lt;p&gt;We need an endpoint that accepts POST requests, validates the input, calls Groq, and returns the result.&lt;/p&gt;

&lt;p&gt;Here is the full src/index.ts implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Groq&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;groq-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;GROQ_API_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. CORS Preflight (handle OPTIONS)&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OPTIONS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Access-Control-Allow-Methods&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST, OPTIONS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Access-Control-Allow-Headers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Only allow POST requests&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Method not allowed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;405&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// 3. Parse and sanitize input&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;promptType&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Missing 'context' field&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// 4. Initialize Groq with the secret key from environment&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;groq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Groq&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GROQ_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="c1"&gt;// 5. Construct a system prompt based on the type (e.g., finance, health)&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;systemPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You are a helpful financial assistant.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;promptType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;health&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;systemPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You are a medical disclaimer assistant. Provide general wellness info only, no diagnoses.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// 6. Call the LLM&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chatCompletion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;groq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;systemPrompt&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Explain what these calculations mean in plain English: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;llama3-8b-8192&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Fast and cheap Groq model&lt;/span&gt;
        &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chatCompletion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;No response generated.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="c1"&gt;// 7. Return the response to the browser with CORS headers&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Proxy error:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Internal server error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Step 4: Calling the Proxy from Your Frontend&lt;/p&gt;

&lt;p&gt;Now, back in your browser-based calculator (Vanilla JS, React, or Vue), you simply call your deployed Worker URL.&lt;/p&gt;

&lt;p&gt;Here is a minimal frontend fetch example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getAIInsight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calculationResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://your-worker-name.workers.dev/api/ai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Principal: $1000, Rate: 5%, Years: 10. Future Value: $1628.89.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;promptType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// e.g., "finance"&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Network error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// The AI explanation&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to fetch AI insight:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Insight unavailable at this moment.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Crucially: Notice that the frontend never knows the API key. Even if an attacker inspects this network request, they only see a call to your Worker, not to Groq/OpenAI.&lt;/p&gt;




&lt;p&gt;Trade-offs and Considerations&lt;/p&gt;

&lt;p&gt;While this pattern is highly effective, it is not magic. Be aware of the following:&lt;/p&gt;

&lt;p&gt;Factor Consideration&lt;br&gt;
Latency Adding a proxy introduces an extra network hop. With Cloudflare's global network, this is usually &amp;lt; 50ms, but it's worth measuring.&lt;br&gt;
Cost Cloudflare Workers have a generous free tier (100k requests/day). However, you are still paying for the LLM tokens. Implement strict max_tokens limits.&lt;br&gt;
Rate Limiting Without accounts, how do you stop abuse? You can implement a simple IP-based rate limiter using Workers KV to prevent a single IP from draining your credits.&lt;br&gt;
CORS If your frontend is on a specific domain, restrict Access-Control-Allow-Origin to that domain instead of using * in production.&lt;/p&gt;




&lt;p&gt;A Real-World Implementation&lt;/p&gt;

&lt;p&gt;This exact architecture is currently running in production on &lt;a href="https://AfriWidget.com/" rel="noopener noreferrer"&gt;AfriWidget.com&lt;/a&gt;. They use a Groq proxy to power AI explanations for their compound interest and GPA calculators.&lt;/p&gt;

&lt;p&gt;The frontend sends only the numerical context—e.g., { principal: 5000, rate: 7, years: 20 }—to the proxy. The Worker appends the system prompt, calls Groq's Llama 3 model, and streams back a plain-English explanation of the financial projection.&lt;/p&gt;

&lt;p&gt;Notably, they do not store user inputs or names anywhere in the proxy logs, ensuring that even if the Worker logs were compromised, no Personally Identifiable Information (PII) would leak.&lt;/p&gt;




&lt;p&gt;Final Thoughts&lt;/p&gt;

&lt;p&gt;Exposing API keys in client-side code is a shortcut that eventually becomes a financial liability. By spending 15 minutes setting up a Cloudflare Worker, you get:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Security: Keys stay out of source control and browser memory.&lt;/li&gt;
&lt;li&gt;Flexibility: You can swap LLM providers (Groq, OpenAI, Anthropic) without redeploying your frontend.&lt;/li&gt;
&lt;li&gt;Observability: You can add logging and error handling in one centralized place.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The proxy pattern isn't just for AI APIs. Apply it to any third-party service that requires a secret. Your future self—and your bank account—will thank you.&lt;/p&gt;




&lt;p&gt;What patterns do you use to secure external API calls? Let me know in the comments, or share your Worker implementation!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tutorial</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>15 Free Online Tools Every Student, Freelancer, and Small Business Owner Should Know</title>
      <dc:creator>Obed Avorlenu</dc:creator>
      <pubDate>Sun, 09 Aug 2026 18:09:38 +0000</pubDate>
      <link>https://dev.to/obed_avorlenu/15-free-online-tools-every-student-freelancer-and-small-business-owner-should-know-31o5</link>
      <guid>https://dev.to/obed_avorlenu/15-free-online-tools-every-student-freelancer-and-small-business-owner-should-know-31o5</guid>
      <description>&lt;p&gt;The internet is full of complicated software, expensive subscriptions, and tools that require you to create an account before you can even use them. But many everyday tasks don't need complicated software—you simply need the right tool for the job.&lt;/p&gt;

&lt;p&gt;Whether you're a student calculating your grades, a freelancer estimating your rates, a business owner working with numbers, or someone trying to manage personal finances, having a collection of simple online tools can save significant time.&lt;/p&gt;

&lt;p&gt;That's why I put together this list of useful free tools available through AfriWidget, a growing collection of calculators and AI-powered tools designed for everyday problems.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;GPA and CGPA Calculators&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Students often have to calculate their GPA or CGPA manually, which can become confusing when different courses have different credit weights.&lt;/p&gt;

&lt;p&gt;A GPA/CGPA calculator allows you to enter your courses, grades, and credit hours and quickly estimate your result.&lt;/p&gt;

&lt;p&gt;This is particularly useful when you're planning your semester or trying to understand how your current grades affect your overall academic performance.&lt;/p&gt;

&lt;p&gt;👉 Try &lt;a href="https://AfriWidget.com/" rel="noopener noreferrer"&gt;AfriWidget's&lt;/a&gt; academic tools to make academic calculations easier.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Savings Goal Calculator&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Saving money is easier when you have a specific target.&lt;/p&gt;

&lt;p&gt;Instead of simply saying "I want to save more," determine how much you need to save each week or month to reach a specific goal.&lt;/p&gt;

&lt;p&gt;For example, if you want to save $1,200 within one year, a savings calculator can help you determine the approximate amount you need to put aside regularly.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Emergency Fund Calculator&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An emergency fund can help protect you from unexpected expenses such as repairs, temporary loss of income, or urgent bills.&lt;/p&gt;

&lt;p&gt;A simple emergency-fund calculator can help you estimate how much money you should aim to keep available based on your monthly expenses and desired number of months of coverage.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Investment Return Calculator&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Investing involves risk, but understanding potential returns can help you make more informed decisions.&lt;/p&gt;

&lt;p&gt;An investment calculator allows you to experiment with variables such as:&lt;/p&gt;

&lt;p&gt;Initial investment&lt;/p&gt;

&lt;p&gt;Monthly contributions&lt;/p&gt;

&lt;p&gt;Expected annual return&lt;/p&gt;

&lt;p&gt;Investment period&lt;/p&gt;

&lt;p&gt;Remember that calculator results are estimates—not guarantees of future investment performance.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Freelance Rate Calculator&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One of the biggest challenges for new freelancers is deciding what to charge.&lt;/p&gt;

&lt;p&gt;Instead of choosing a random hourly rate, consider your target income, working hours, expenses, taxes, and the amount of time you actually expect to spend working.&lt;/p&gt;

&lt;p&gt;A freelance rate calculator can give you a starting point for determining a reasonable rate.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;AI Cover Letter Generator&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Writing a different cover letter for every job application can take a lot of time.&lt;/p&gt;

&lt;p&gt;An AI-powered cover letter generator can help you create a first draft based on your experience and the position you're applying for.&lt;/p&gt;

&lt;p&gt;You should always review and personalize the generated letter before submitting it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;AI Freelance Bio Generator&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your freelance bio is often one of the first things a potential client sees.&lt;/p&gt;

&lt;p&gt;A good bio should quickly communicate:&lt;/p&gt;

&lt;p&gt;What you do&lt;/p&gt;

&lt;p&gt;Who you help&lt;/p&gt;

&lt;p&gt;Your skills&lt;/p&gt;

&lt;p&gt;The value you provide&lt;/p&gt;

&lt;p&gt;An AI-powered bio generator can help you create a professional starting point that you can then customize.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Gig Description Generator&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're selling services on freelance marketplaces, writing descriptions for every service can become repetitive.&lt;/p&gt;

&lt;p&gt;An AI gig-description tool can help you structure your offer and explain your service more clearly.&lt;/p&gt;

&lt;p&gt;The important part is to make sure the final description accurately represents what you actually provide.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Side Hustle Finder&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Not every side hustle works for everyone.&lt;/p&gt;

&lt;p&gt;Your available time, skills, equipment, location, and starting budget all matter.&lt;/p&gt;

&lt;p&gt;A side-hustle finder can help you explore ideas based on your circumstances instead of randomly choosing something from a list of popular trends.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Currency Converter&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;People increasingly work, shop, study, and communicate internationally.&lt;/p&gt;

&lt;p&gt;A currency converter can be useful when comparing prices, freelance payments, international salaries, or expenses in different currencies.&lt;/p&gt;

&lt;p&gt;For financial decisions, however, remember that actual bank, card, or payment-provider exchange rates may differ from a displayed reference rate.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;BMI Calculator&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A BMI calculator uses height and weight to calculate a basic body-mass index.&lt;/p&gt;

&lt;p&gt;BMI can be useful as a general screening measure, but it isn't a complete assessment of someone's health or body composition. It should not be treated as a diagnosis.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Percentage Calculator&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Percentages appear everywhere:&lt;/p&gt;

&lt;p&gt;Discounts&lt;/p&gt;

&lt;p&gt;Business growth&lt;/p&gt;

&lt;p&gt;Exam scores&lt;/p&gt;

&lt;p&gt;Profit margins&lt;/p&gt;

&lt;p&gt;Salary increases&lt;/p&gt;

&lt;p&gt;Interest calculations&lt;/p&gt;

&lt;p&gt;Statistics&lt;/p&gt;

&lt;p&gt;A dedicated percentage calculator can save you from repeatedly doing the same calculations manually.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Loan Calculator&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before considering a loan, it can be useful to estimate potential monthly payments.&lt;/p&gt;

&lt;p&gt;A loan calculator can help you experiment with:&lt;/p&gt;

&lt;p&gt;Loan amount + interest rate + repayment period = estimated payment&lt;/p&gt;

&lt;p&gt;The actual amount you pay may vary depending on fees, lender policies, compounding methods, and other terms.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Random Generator Tools&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Sometimes you simply need something random.&lt;/p&gt;

&lt;p&gt;Random generators can be useful for:&lt;/p&gt;

&lt;p&gt;Choosing names&lt;/p&gt;

&lt;p&gt;Generating numbers&lt;/p&gt;

&lt;p&gt;Creating ideas&lt;/p&gt;

&lt;p&gt;Selecting options&lt;/p&gt;

&lt;p&gt;Testing applications&lt;/p&gt;

&lt;p&gt;Brainstorming&lt;/p&gt;

&lt;p&gt;Developers can also use simple generators during testing without building their own scripts.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Why Use a Collection of Small Tools?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The biggest advantage isn't necessarily having one complicated application.&lt;/p&gt;

&lt;p&gt;Sometimes, small tools that solve specific problems are more useful.&lt;/p&gt;

&lt;p&gt;If you need to calculate your GPA, you don't need project-management software. If you need to estimate a freelance rate, you don't need an enterprise accounting platform.&lt;/p&gt;

&lt;p&gt;You just need a tool that solves the problem quickly.&lt;/p&gt;

&lt;p&gt;That's the idea behind AfriWidget: bringing useful calculators and AI-powered tools together in one place so users can solve everyday problems without searching across dozens of different websites.&lt;/p&gt;

&lt;p&gt;A Better Approach to Online Tools&lt;/p&gt;

&lt;p&gt;When using online calculators and AI tools, don't blindly accept the result.&lt;/p&gt;

&lt;p&gt;Instead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Understand what the tool is calculating.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check the information you entered.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Review the result.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Compare important calculations when necessary.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use professional advice for high-stakes financial, medical, or legal decisions.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Tools should help you make decisions—not replace your judgment.&lt;/p&gt;

&lt;p&gt;Final Thoughts&lt;/p&gt;

&lt;p&gt;The best online tool isn't necessarily the one with the most features. It's often the one that solves your problem quickly, clearly, and without unnecessary complexity.&lt;/p&gt;

&lt;p&gt;From academic calculators and financial tools to AI-powered career and freelancing tools, AfriWidget brings a growing collection of practical tools into one platform.&lt;/p&gt;

&lt;p&gt;Explore the free tools at AfriWidget and find one that can make your next task easier.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tooling</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Beyond the Code: Why Remote African Developers Need a "Financial Tech Stack"</title>
      <dc:creator>Obed Avorlenu</dc:creator>
      <pubDate>Fri, 17 Jul 2026 16:10:35 +0000</pubDate>
      <link>https://dev.to/obed_avorlenu/beyond-the-code-why-remote-african-developers-need-a-financial-tech-stack-28jm</link>
      <guid>https://dev.to/obed_avorlenu/beyond-the-code-why-remote-african-developers-need-a-financial-tech-stack-28jm</guid>
      <description>&lt;p&gt;We spend thousands of hours optimizing our IDEs, perfecting our CI/CD pipelines, and mastering new frameworks. But as a developer working remotely, I realized I was putting more effort into my code than I was into the financial infrastructure that supports my life.&lt;br&gt;
​If you are a developer based in Africa landing remote roles, you’re dealing with a specific set of variables that developers in other regions don't face: international exchange rates, complex cross-border tax implications, and the challenge of building long-term wealth when your income is in USD but your expenses are in local currency.&lt;br&gt;
​The "Financial Debt" in Your Career&lt;br&gt;
​In software, we know that technical debt slows down development. In your personal life, "financial debt" works the same way. It manifests when:&lt;br&gt;
​You don't have a system to hedge against local currency volatility.&lt;br&gt;
​You underprice your freelance contracts because you aren't using data-driven negotiation strategies.&lt;br&gt;
​You are manually calculating taxes and conversion fees instead of automating your "take-home" analysis.&lt;br&gt;
​To solve this, you need to build a "Financial Tech Stack"—a set of automated tools and mental models that manage your money with the same efficiency you apply to your deployments.&lt;br&gt;
​Building Your Personal Financial Infrastructure&lt;br&gt;
​1. The Negotiation Layer (Data-Driven Compensation)&lt;br&gt;
​Many developers fall into the trap of accepting the first offer because they don't have a clear benchmark. Don't just guess your value. You should be calculating your worth based on:&lt;br&gt;
​Purchasing Power Parity (PPP): Understanding what your rate means in a global market versus your local cost of living.&lt;br&gt;
​The "Premium" Factor: Remote roles often require specialized asynchronous communication skills. Your rate should reflect your ability to work autonomously, not just your ability to commit code.&lt;br&gt;
​2. The Conversion Layer (Managing Volatility)&lt;br&gt;
​When your income arrives in foreign currency, you are essentially managing a micro-treasury. If you wait until the end of the month to convert your money, you are subject to whatever rate the bank gives you that day.&lt;br&gt;
​Automation: Use tools to track currency trends and automate your conversion at optimal times.&lt;br&gt;
​Fees vs. Speed: Calculate the hidden cost of traditional wire transfers versus modern digital wallets or crypto-rails. The "cheapest" service is often the one that saves you 3% in fees per transaction.&lt;br&gt;
​3. The Scalability Layer (Investing for the Future)&lt;br&gt;
​Your career as a developer has a "compounding interest" effect, but only if you reinvest your capital. The goal isn't just to earn USD; it’s to move that capital into assets—whether that’s stocks, local real estate, or business ventures—that grow while you sleep. If you are keeping all your earnings in a basic savings account, inflation is effectively acting as a "memory leak" in your portfolio.&lt;br&gt;
​Bridging the Gap: Tools for the African Context&lt;br&gt;
​I got tired of using "global" tools that didn't understand the realities of my bank, my currency, or my career path. Standard calculators assume stable banking and low-friction markets, which often isn't the case here.&lt;br&gt;
​Because of this, I built a set of 20 AI-powered tools at &lt;a href="https://vectric.online/" rel="noopener noreferrer"&gt;VectricEarn&lt;/a&gt; to fill this gap. I designed these to function as the missing components in your financial tech stack:&lt;br&gt;
​Remote Salary Benchmarking: AI-driven frameworks to help you negotiate better contracts based on global standards.&lt;br&gt;
​Currency &amp;amp; Budgeting Calculators: Tools that account for the local reality of living in markets like Nigeria, Kenya, or Ghana, so you can track your "true" income.&lt;br&gt;
​Side Hustle ROI Analysis: A way to calculate if your extra projects are actually profitable, factoring in your time and current operational costs.&lt;br&gt;
​How to Get Started Today&lt;br&gt;
​If you want to treat your career like a professional project, start here:&lt;br&gt;
​Audit your current "Financial Stack." Where are you losing money to fees or poor exchange rate management?&lt;br&gt;
​Define your KPIs. What is your target monthly savings rate? What is your target hourly rate for the next 6 months?&lt;br&gt;
​Automate. Stop doing manual math. Use tools (like the ones I’ve listed above) to handle the logic so you can focus on building your next feature.&lt;br&gt;
​We are the architects of the future of tech in Africa. But we can't build great things if we are constantly stressed about our financial stability. Stop treating your personal finances as an afterthought. Build an infrastructure for your money that is just as robust as the code you write.&lt;br&gt;
​What's your "Financial Tech Stack"?&lt;br&gt;
​Are you currently using specific tools to manage your remote salary or handle your local finances? Have you found ways to automate your currency conversion or tax planning? Let’s discuss in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Build a Side Hustle Finder Web App with Next.js A Practical Step‑by‑Step Guide Inspired by VectricEarn</title>
      <dc:creator>Obed Avorlenu</dc:creator>
      <pubDate>Fri, 03 Jul 2026 12:57:52 +0000</pubDate>
      <link>https://dev.to/obed_avorlenu/build-a-side-hustle-finder-web-app-with-nextjs-a-practical-step-by-step-guide-inspired-by-14i3</link>
      <guid>https://dev.to/obed_avorlenu/build-a-side-hustle-finder-web-app-with-nextjs-a-practical-step-by-step-guide-inspired-by-14i3</guid>
      <description>&lt;p&gt;TL;DR – In this tutorial, you’ll build a full‑stack web app that helps users discover side hustles, save favourites, and follow practical guides. The content is inspired by the kind of actionable advice you'd find on &lt;a href="https://vectric.online/" rel="noopener noreferrer"&gt;VectricEarn&lt;/a&gt; – a website that covers personal finance, remote jobs, side hustles, investing, career growth, and scholarships for Africans. Your finished app can become a portfolio piece, a community tool, or even a monetisable product.&lt;/p&gt;




&lt;p&gt;Why This Project?&lt;/p&gt;

&lt;p&gt;Across Africa, young people are looking for practical ways to earn extra income, grow their careers, and build wealth. Resources like &lt;a href="https://vectric.online/" rel="noopener noreferrer"&gt;VectricEarn&lt;/a&gt; provide valuable guides on:&lt;/p&gt;

&lt;p&gt;· Personal Finance – Budgeting, saving, and investing in the African context&lt;br&gt;
· Remote Jobs &amp;amp; Freelancing – Legitimate work opportunities that pay in foreign currency&lt;br&gt;
· Side Hustles – Step‑by‑step guides to earning outside your 9‑to‑5&lt;br&gt;
· Investing &amp;amp; Wealth Building – Stocks, ETFs, real estate, and crypto explained simply&lt;br&gt;
· Career Growth – Salary negotiation, promotions, and in‑demand skills&lt;br&gt;
· Scholarships &amp;amp; Opportunities – Fully funded programs and grants&lt;/p&gt;

&lt;p&gt;This Side Hustle Finder app complements that mission by helping users:&lt;/p&gt;

&lt;p&gt;· Browse hustle ideas filtered by category, startup cost, and difficulty&lt;br&gt;
· Read step‑by‑step guides for each hustle&lt;br&gt;
· Save favourite hustles to a personal dashboard&lt;br&gt;
· Track estimated income and progress&lt;/p&gt;

&lt;p&gt;What’s in it for you?&lt;br&gt;
Building this app teaches you:&lt;/p&gt;

&lt;p&gt;· Full‑stack development with Next.js (App Router + API routes)&lt;br&gt;
· Database modelling with Prisma and SQLite&lt;br&gt;
· Authentication with NextAuth.js&lt;br&gt;
· Modern frontend with Tailwind CSS&lt;br&gt;
· How to turn a real‑world problem into a software solution&lt;/p&gt;

&lt;p&gt;And you can earn from it too: use it as a portfolio project to land freelance gigs, or package it as a SaaS for local entrepreneurs.&lt;/p&gt;



&lt;p&gt;Prerequisites&lt;/p&gt;

&lt;p&gt;· Basic knowledge of JavaScript/TypeScript and React&lt;br&gt;
· Node.js 18+ installed&lt;br&gt;
· A code editor (VS Code recommended)&lt;br&gt;
· Familiarity with the command line&lt;/p&gt;



&lt;p&gt;Step 1: Set Up Your Next.js Project&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a new Next.js project with TypeScript and Tailwind CSS&lt;/span&gt;
npx create-next-app@latest side-hustle-finder &lt;span class="nt"&gt;--typescript&lt;/span&gt; &lt;span class="nt"&gt;--tailwind&lt;/span&gt; &lt;span class="nt"&gt;--app&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;side-hustle-finder

&lt;span class="c"&gt;# Install dependencies&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; @prisma/client prisma next-auth @types/next-auth bcryptjs
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; @types/bcryptjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Step 2: Set Up the Database with Prisma&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx prisma init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit prisma/schema.prisma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

model User {
  id        String   @id @default(cuid())
  username  String   @unique
  email     String   @unique
  password  String   // hashed
  savedHustles SavedHustle[]
}

model Hustle {
  id        String   @id @default(cuid())
  title     String
  category  String
  startupCost String
  difficulty String
  description String
  steps     String   // JSON array of practical steps
  estimatedIncome String
  savedBy   SavedHustle[]
}

model SavedHustle {
  id        String @id @default(cuid())
  userId    String
  hustleId  String
  user      User @relation(fields: [userId], references: [id])
  hustle    Hustle @relation(fields: [hustleId], references: [id])

  @@unique([userId, hustleId])
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set .env:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;DATABASE_URL&lt;/span&gt;=&lt;span class="s2"&gt;"file:./dev.db"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run migration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx prisma migrate dev &lt;span class="nt"&gt;--name&lt;/span&gt; init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Step 3: Seed the Database with Real Hustle Ideas&lt;/p&gt;

&lt;p&gt;Create prisma/seed.ts (install ts-node as dev dependency first). These ideas are inspired by the practical guides you'd find on VectricEarn – covering side hustles that actually work in the African context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PrismaClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@prisma/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PrismaClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hustles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AI‑Powered Tutoring via WhatsApp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Teaching&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;startupCost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;difficulty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Beginner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Use ChatGPT + WhatsApp to teach subjects you already know.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ask ChatGPT to create a lesson plan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Design a flyer using Canva&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Share on WhatsApp status and groups&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Teach via voice notes and shared docs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;estimatedIncome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$10‑$50 per student&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Canva + AI Micro‑Designs for Local Shops&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Design&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;startupCost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;difficulty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Beginner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Create posters, menus, and flyers for local businesses using Canva and AI.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Find 5 small shops in your neighbourhood&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Use ChatGPT to write catchy slogans&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Design posters in Canva&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Show samples and offer 3 designs for $10&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;estimatedIncome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$10‑$30 per design&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Virtual Research Assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Writing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;startupCost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;difficulty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Intermediate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Help students and organisations with research using ChatGPT.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Join student groups on Facebook/LinkedIn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Offer to summarise papers or find references&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Use Google Docs to deliver summaries&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Polish AI output in your own voice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;estimatedIncome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$20‑$50 per project&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Social Media Caption Micro‑Agency&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Marketing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;startupCost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;difficulty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Intermediate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Write captions and manage social media for local businesses.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Pick 3 local brands&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ask ChatGPT for caption ideas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Schedule posts with Meta Business Suite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Offer management for $30‑$50/month&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;estimatedIncome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$50‑$150/month&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Remote Freelance Writing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Writing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;startupCost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;difficulty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Intermediate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Write for international clients on platforms like Upwork and Fiverr.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Choose a niche (tech, finance, lifestyle)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Build a portfolio with sample articles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Create profiles on Upwork and Fiverr&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Start with smaller gigs to build reputation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;estimatedIncome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$100‑$1000+ per month&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;hustles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hustle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
      &lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$disconnect&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add seed script in package.json:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"prisma"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"seed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ts-node --compiler-options {&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;module&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;CommonJS&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;} prisma/seed.ts"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run seeding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx prisma db seed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Step 4: Set Up Authentication (NextAuth.js)&lt;/p&gt;

&lt;p&gt;Create src/app/api/auth/[...nextauth]/route.ts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;NextAuth&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next-auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;CredentialsProvider&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next-auth/providers/credentials&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PrismaAdapter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@next-auth/prisma-adapter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PrismaClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@prisma/client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;bcrypt&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bcryptjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PrismaClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;PrismaAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;CredentialsProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Credentials&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isValid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bcrypt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isValid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jwt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;callbacks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;signIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NextAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;POST&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Step 5: Build API Routes&lt;/p&gt;

&lt;p&gt;src/app/api/hustles/route.ts – GET all with filters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PrismaClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@prisma/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PrismaClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;category&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cost&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;difficulty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;difficulty&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;All&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;where&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;where&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startupCost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;difficulty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;where&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;difficulty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;difficulty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hustles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hustle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;where&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hustles&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;src/app/api/hustles/[id]/route.ts – GET single:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PrismaClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@prisma/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PrismaClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hustle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hustle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;hustle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hustle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;src/app/api/saved/route.ts – POST save, DELETE unsave (protected):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getServerSession&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-auth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;authOptions&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../auth/[...nextauth]/route&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PrismaClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@prisma/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PrismaClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getServerSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;hustleId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;savedHustle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hustleId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;DELETE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getServerSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;hustleId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;savedHustle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;userId_hustleId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hustleId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Step 6: Build the Frontend Pages&lt;/p&gt;

&lt;p&gt;Homepage (src/app/page.tsx)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useSession&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-auth/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Hustle&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@prisma/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSession&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;hustles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setHustles&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Hustle&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCategories&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/hustles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setHustles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="na"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Hustle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;setCategories&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;All&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cats&lt;/span&gt;&lt;span class="p"&gt;)]);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"container mx-auto p-4"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-3xl font-bold mb-4"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Find Your Next Side Hustle 💰&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-lg mb-6"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Discover legitimate ways to earn extra income — built for the African context.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex gap-4 mb-6"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;select&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"border p-2 rounded"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;All Categories&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;select&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;select&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"border p-2 rounded"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;All Costs&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;$0&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;$10-50&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;$50+&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;select&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;hustles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"border rounded-lg p-4 shadow"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-xl font-semibold"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex gap-2 my-2"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bg-gray-200 px-2 py-1 rounded text-sm"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bg-blue-100 px-2 py-1 rounded text-sm"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;difficulty&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bg-green-100 px-2 py-1 rounded text-sm"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startupCost&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-gray-600"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mt-4 flex justify-between items-center"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`/hustle/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-blue-600 hover:underline"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;View Guide&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
                  &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/saved&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;hustleId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
                  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                  &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bg-green-500 text-white px-3 py-1 rounded"&lt;/span&gt;
                &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                  Save
                &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mt-2 text-sm text-gray-500"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Est. income: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;estimatedIncome&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Detail Page (src/app/hustle/[id]/page.tsx)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PrismaClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@prisma/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;notFound&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/navigation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getServerSession&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-auth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;authOptions&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/app/api/auth/[...nextauth]/route&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PrismaClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HustlePage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getServerSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hustle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hustle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;hustle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;notFound&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hustle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"container mx-auto p-4"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;nav&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-sm mb-4"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-blue-600"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Home&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; / &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;hustle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;nav&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-3xl font-bold"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;hustle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex gap-2 my-3"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bg-gray-200 px-2 py-1 rounded"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;hustle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bg-blue-100 px-2 py-1 rounded"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;hustle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;difficulty&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bg-green-100 px-2 py-1 rounded"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Startup: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;hustle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startupCost&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bg-yellow-100 px-2 py-1 rounded"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Est. income: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;hustle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;estimatedIncome&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-lg"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;hustle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-2xl font-semibold mt-6"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Step‑by‑Step Guide&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ol&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"list-decimal list-inside space-y-2 mt-2"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ol&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
          &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/saved&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;hustleId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hustle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mt-6 bg-green-500 text-white px-4 py-2 rounded"&lt;/span&gt;
        &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          Save to Dashboard
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/login"&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mt-6 inline-block text-blue-600"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Login to save&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mt-4 inline-block ml-4 text-gray-600"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Back to all&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dashboard (src/app/dashboard/page.tsx) – protected&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getServerSession&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-auth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;authOptions&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/app/api/auth/[...nextauth]/route&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PrismaClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@prisma/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;redirect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/navigation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PrismaClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Dashboard&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getServerSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;savedHustle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;hustle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"container mx-auto p-4"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-2xl font-bold"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;My Saved Hustles&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;saved&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;You haven't saved any hustles yet. &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-blue-600"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Browse all&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"space-y-4 mt-4"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;saved&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;hustle&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;hustle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"border p-4 rounded flex justify-between items-center"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`/hustle/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hustle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-xl font-medium"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;hustle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mr-4 text-sm"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;hustle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;estimatedIncome&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
                  &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/saved&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DELETE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;hustleId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hustle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
                  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                  &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bg-red-500 text-white px-3 py-1 rounded"&lt;/span&gt;
                &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                  Remove
                &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Login &amp;amp; Register Pages&lt;/p&gt;

&lt;p&gt;Simple client‑side forms using next-auth/react – implement with standard forms and call signIn('credentials', { email, password }).&lt;/p&gt;




&lt;p&gt;Step 7: Run Your App&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;. Register, log in, explore, and save hustles.&lt;/p&gt;




&lt;p&gt;How to Monetise This Project&lt;/p&gt;

&lt;p&gt;· White‑Label it – Sell the app to local businesses, universities, or co‑working spaces as a tool for their communities.&lt;br&gt;
· Offer Custom Content – Charge to add more hustles (like those covered on [Next.js and tailor the guides to specific regions.&lt;br&gt;
· Build a Community – Add forums, success stories, and monetise through ads or premium listings.&lt;br&gt;
· Showcase in Your Portfolio – Use it to land freelance or full‑time roles in web development.&lt;/p&gt;




&lt;p&gt;Next Steps&lt;/p&gt;

&lt;p&gt;· Add user‑submitted hustles (moderated) – this could turn into a crowdsourced library of practical guides.&lt;br&gt;
· Implement income tracking – let users log their actual earnings to see progress.&lt;br&gt;
· Integrate payment gateways for premium features.&lt;br&gt;
· Improve filters with server‑side pagination and search.&lt;/p&gt;




&lt;p&gt;Conclusion&lt;/p&gt;

&lt;p&gt;You've built a full‑stack, production‑ready Next.js application that addresses a genuine need for Africans looking to earn extra income. The data and ideas used are drawn from real‑world opportunities – the kind you'd find on resources like VectricEarn, which provides practical guides on personal finance, remote jobs, side hustles, investing, career growth, and scholarships.&lt;/p&gt;

&lt;p&gt;This project is not just a coding exercise; it's a potential revenue stream. Deploy it, iterate, and watch it grow.&lt;/p&gt;

&lt;p&gt;Happy building – and happy earning! 🚀&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>nextjs</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Build Your First AI App: A Code-First Guide for Beginners</title>
      <dc:creator>Obed Avorlenu</dc:creator>
      <pubDate>Tue, 30 Jun 2026 07:18:24 +0000</pubDate>
      <link>https://dev.to/obed_avorlenu/build-your-first-ai-app-a-code-first-guide-for-beginners-1g4j</link>
      <guid>https://dev.to/obed_avorlenu/build-your-first-ai-app-a-code-first-guide-for-beginners-1g4j</guid>
      <description>&lt;p&gt;Forget the high-level theory and endless debates about artificial general intelligence. If you are a developer looking to actually build with AI, the best way to learn is by writing code.&lt;br&gt;
​In this guide, we are going to build a functional AI text generator using Next.js, TypeScript, and the OpenAI API. No fluff, no abstract concepts—just line-by-line code and practical implementation.&lt;br&gt;
​Prerequisites&lt;br&gt;
​Before writing any code, ensure you have the following ready:&lt;br&gt;
​Node.js installed on your machine.&lt;br&gt;
​A code editor (VS Code is recommended).&lt;br&gt;
​An OpenAI API Key (Grab one from the OpenAI developer dashboard).&lt;br&gt;
​Step 1: Project Setup&lt;br&gt;
​We are using Next.js (App Router) for this build because it allows us to handle both our frontend UI and our backend API routes in a single repository.&lt;br&gt;
​Open your terminal and run:&lt;br&gt;
npx create-next-app@latest my-first-ai-app&lt;br&gt;
During the setup, select the following options:&lt;br&gt;
​TypeScript: Yes&lt;br&gt;
​ESLint: Yes&lt;br&gt;
​Tailwind CSS: Yes&lt;br&gt;
​src/ directory: No&lt;br&gt;
​App Router: Yes&lt;br&gt;
​Customize default import alias: No&lt;br&gt;
​Navigate into your new project and install the official OpenAI SDK:cd my-first-ai-app&lt;br&gt;
npm install openai&lt;br&gt;
Create a file named .env.local in the root of your project to securely store your API key:&lt;br&gt;
OPENAI_API_KEY=your_actual_api_key_here&lt;br&gt;
Never commit this file to GitHub. Next.js automatically ignores .env.local by default).&lt;br&gt;
Step 2: Building the API Route (The Backend)&lt;br&gt;
We need a secure place to execute our AI requests so our API key isn't exposed to the client. Next.js Route Handlers are perfect for this.&lt;br&gt;
Create a new file at app/api/generate/route.ts and add the following code&lt;br&gt;
import { NextResponse } from 'next/server';&lt;br&gt;
import OpenAI from 'openai';&lt;/p&gt;

&lt;p&gt;// Initialize the OpenAI client with your API key&lt;br&gt;
const openai = new OpenAI({&lt;br&gt;
  apiKey: process.env.OPENAI_API_KEY,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;export async function POST(request: Request) {&lt;br&gt;
  try {&lt;br&gt;
    // Parse the incoming JSON payload from the frontend&lt;br&gt;
    const body = await request.json();&lt;br&gt;
    const { prompt } = body;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Validate that a prompt was actually provided
if (!prompt) {
  return NextResponse.json(
    { error: 'Prompt is required' },
    { status: 400 }
  );
}

// Call the OpenAI API using the gpt-3.5-turbo model
const completion = await openai.chat.completions.create({
  model: 'gpt-3.5-turbo',
  messages: [
    {
      role: 'system',
      content: 'You are a helpful and concise AI assistant.',
    },
    {
      role: 'user',
      content: prompt,
    },
  ],
  temperature: 0.7,
  max_tokens: 500,
});

// Extract the AI's response text
const aiResponse = completion.choices[0].message.content;

// Send the response back to the client
return NextResponse.json({ result: aiResponse });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;} catch (error) {&lt;br&gt;
    // Handle errors (e.g., rate limits, invalid keys)&lt;br&gt;
    console.error('AI API Error:', error);&lt;br&gt;
    return NextResponse.json(&lt;br&gt;
      { error: 'Failed to generate response' },&lt;br&gt;
      { status: 500 }&lt;br&gt;
    );&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
Line-by-Line Breakdown:&lt;br&gt;
​import { NextResponse }...: We import Next.js's built-in response handler to send data back to the browser.&lt;br&gt;
​const openai = new OpenAI(...): This instantiates the OpenAI client. It automatically looks for process.env.OPENAI_API_KEY.&lt;br&gt;
​export async function POST(request: Request): This defines an HTTP POST endpoint. AI generation should always be a POST request because we are sending a payload (the prompt).&lt;br&gt;
​const body = await request.json(): We extract the JSON body sent from our frontend.&lt;br&gt;
​openai.chat.completions.create(...): This is the core engine. We specify the model (GPT-3.5-turbo is fast and cheap for beginners), set the system message to define the AI's personality, and pass the user prompt.&lt;br&gt;
​temperature: 0.7: Controls creativity. 0 is robotic and exact, 1 is highly creative. 0.7 is a good middle ground.&lt;br&gt;
​completion.choices[0].message.content: The OpenAI API returns a deeply nested JSON object. This specific path extracts just the raw text of the AI's answer.&lt;br&gt;
​Step 3: Building the User Interface (The Frontend)&lt;br&gt;
​Now, let's build the visual interface where users can type their prompts and read the AI's responses.&lt;br&gt;
​Open app/page.tsx, delete the boilerplate code, and replace it with this:&lt;/p&gt;

&lt;p&gt;'use client';&lt;/p&gt;

&lt;p&gt;import { useState } from 'react';&lt;/p&gt;

&lt;p&gt;export default function Home() {&lt;br&gt;
  // State variables to manage inputs, outputs, and loading status&lt;br&gt;
  const [input, setInput] = useState('');&lt;br&gt;
  const [output, setOutput] = useState('');&lt;br&gt;
  const [isLoading, setIsLoading] = useState(false);&lt;/p&gt;

&lt;p&gt;// The function triggered when the user clicks "Generate"&lt;br&gt;
  const handleSubmit = async (e: React.FormEvent) =&amp;gt; {&lt;br&gt;
    e.preventDefault();&lt;br&gt;
    if (!input) return;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;setIsLoading(true);
setOutput('');

try {
  // Make a POST request to our custom API route
  const response = await fetch('/api/generate', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ prompt: input }),
  });

  const data = await response.json();

  // Check for errors returned by the backend
  if (!response.ok) {
    throw new Error(data.error || 'Something went wrong');
  }

  // Update the UI with the AI's response
  setOutput(data.result);
} catch (error: any) {
  setOutput(`Error: ${error.message}`);
} finally {
  setIsLoading(false);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;};&lt;/p&gt;

&lt;p&gt;return (&lt;br&gt;
    &lt;/p&gt;
&lt;br&gt;
      &lt;br&gt;
        &lt;h1&gt;
&lt;br&gt;
          My First AI Generator&lt;br&gt;
        &lt;/h1&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    &amp;lt;form onSubmit={handleSubmit} className="flex flex-col gap-4"&amp;gt;
      &amp;lt;textarea
        className="w-full p-4 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 text-black"
        rows={4}
        placeholder="Ask the AI something..."
        value={input}
        onChange={(e) =&amp;gt; setInput(e.target.value)}
      /&amp;gt;

      &amp;lt;button
        type="submit"
        disabled={isLoading}
        className="bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:bg-blue-300 transition-colors"
      &amp;gt;
        {isLoading ? 'Generating...' : 'Generate Response'}
      &amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;

    {output &amp;amp;&amp;amp; (
      &amp;lt;div className="mt-8 p-6 bg-gray-100 rounded-md border border-gray-200 text-black"&amp;gt;
        &amp;lt;h2 className="text-sm font-semibold text-gray-500 mb-2"&amp;gt;AI Response:&amp;lt;/h2&amp;gt;
        &amp;lt;p className="whitespace-pre-wrap"&amp;gt;{output}&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
    )}
  &amp;lt;/div&amp;gt;
&amp;lt;/main&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;);&lt;br&gt;
}&lt;br&gt;
Line-by-Line Breakdown:&lt;br&gt;
​'use client';: A required Next.js directive indicating this component runs in the browser, allowing us to use React Hooks.&lt;br&gt;
​const [input, setInput] = useState('');: Manages the text the user types into the textarea.&lt;br&gt;
​const [output, setOutput] = useState('');: Stores the final response returned by the AI.&lt;br&gt;
​const [isLoading, setIsLoading] = useState(false);: Prevents the user from spamming the submit button while waiting for the API.&lt;br&gt;
​const response = await fetch('/api/generate', ...): We call the backend route we created in Step 2, passing the user's input as a JSON string.&lt;br&gt;
​if (!response.ok): Catch backend errors (like a 400 or 500 status code) and throw an exception to be caught in our catch block.&lt;br&gt;
​className="...": We use Tailwind CSS utility classes to rapidly style the app.&lt;br&gt;
​whitespace-pre-wrap: A crucial CSS class for AI text formatting. It ensures that line breaks and paragraphs generated by the AI are actually rendered on the screen, rather than mashed into a single block of text.&lt;br&gt;
​Step 4: Run and Test&lt;br&gt;
​Start your development server:&lt;br&gt;
npm run dev&lt;br&gt;
Open &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; in your browser. Type a prompt like "Write a two-sentence horror story" into the box and hit Generate. You have just built a fully functional, full-stack AI application.&lt;br&gt;
​The Next Step: Monetizing Your AI Skill&lt;br&gt;
​Writing the code is only the first half of the equation; the second half is turning that technical execution into a tangible income stream. Knowing how to wire up an API isn't enough if you don't know how to package and sell the result.&lt;br&gt;
​Once you have mastered these basics, you can monetize this skill in several ways:&lt;br&gt;
​Micro-SaaS: Build a niche AI tool (e.g., an AI cover letter generator for a specific industry) and charge a monthly subscription.&lt;br&gt;
​Freelance Integration: Many traditional businesses want AI integrated into their existing sites but don't know how. You can offer services to build custom AI chatbots for their customer service.&lt;br&gt;
​Digital Products: Package specific, highly optimized prompts and small scripts into templates that you can sell to non-developers.&lt;br&gt;
​If you are looking for practical, actionable steps on how to transition from simply writing code to actually monetizing digital products and tech skills, &lt;a href="https://vectric.online/" rel="noopener noreferrer"&gt;VectricEarn&lt;/a&gt; is an excellent resource to study. It focuses heavily on the business side of content, remote opportunities, and digital monetization, providing the blueprints needed to turn your new AI development skills into actual revenue.&lt;br&gt;
​Keep building, keep shipping, and start looking at your code as a product.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>firstyearincode</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
