Enjoy the detours!

pelletyze

Displays the error message: “Looks like your CSV is not formatted correctly”

Finally, I found a solution to throw an error at the frontend, while importing wrong CSV Data on the server. For Fast-CSV, I needed a solution to parse the data and put the data into the database. While this part was easy, the error message from Supabase was swallowed, and I had no practical option to present the error to the frontend. Or, there was no uncomplicated way, since now. :D

const stream = csv
  .parse(options)
  .on("error", (error) => {
    console.log(error);
  })
  .on("data", (row) => {
    result.push(row);
  })
  .on("end", async () => {
    const supabase = await createClient();
    const { error, statusText } = await supabase
      .from("fillings_import")
      .upsert(completeFillings(result))
      .select();

    if (error) {
      throw new Error(`${error.message}, ${statusText}`);
    }
  });

stream.write(fileData);
stream.end();

This was my initial solution, and the error at the end was never shown. 🤷

The new version is wrapped with a new Promise and calls reject if adding data to Supabase produces an error. Easy, right? I'm so happy that I don't need to wrap an <ErrorBoundary /> or something else.

await new Promise<void>((resolve, reject) => {
  const stream = csv
    .parse(options)
    .on("error", (error) => {
      console.log(error);
    })
    .on("data", (row) => {
      result.push(row);
    })
    .on("end", async () => {
      const supabase = await createClient();
      const { error, statusText } = await supabase
        .from("fillings_import")
        .upsert(completeFillings(result))
        .select();

      if (error) {
        console.error({ error, statusText, result });
        reject(new Error("Looks like your CSV is not formatted correctly"));
      }

      resolve();
    });

  stream.write(fileData);
  stream.end();
});

This little fix makes me happier than it perhaps should. But in the end, that it. I thought much more about a solution than it took time to write the code. I thought at least on and off for 3 days about it. And today, the “a-ha” moment under the shower. 😎


14 of #100DaysToOffload
#pelletyze
Thoughts? Discuss...

In the last few months, there were some big releases. ESLint v9 was one of them. Sadly, do to their new config approach, I was unable to upgrade Next.js. While waiting for Vercel to fix it, Tailwind CSS v4 and next-intl v4 were released. So I faced 4 big releases.

The first one I did was next-intl v4. They wrote a nice blog post before v4 was released. So you were able to prepare your codebase before the switch, which was great and worked very well and without problems. 👍

Next in line was Tailwind CSS v4. And with it, Tailwind-Merge v3. You need to upgrade them together. Because twMerge dropped tw v3 support. Basically, this worked flawlessly. But some adjustments needed to be done. I use the reset.css from Josh W Comeau which needs special treatment when used with Tailwind CSS v4. Furthermore, I've added some custom styling to the html, body and h1...h6 tags. The result was that I had to wrap them to the correct layers for, so they will not collide with TWv4.

@import "./reset.css" layer(base);
@import "./custom.css" layer(base);

While using the codemod from TWv4 to migrate everything, it swallowed my custom classes. Which I had to add back manually. The new solution is with CSS only and is straightforward.

@utility border-image-highlight-b {
  border-width: (--tw-border-width);
  border-image: linear-gradient(
      to right,
      var(--tw-gradient-from),
      var(--tw-gradient-to)
    )
    1;
}

That's it. The rest was done via the codemod. :)

After the TWv4 upgrade was done, I continued with the Next.js v15 upgrade. Next.js also brought a codemod with it which did its job also very code. But it had an easy job here because I did nothing special. It changed some code in the code, which I had to clean up a bit afterward. Because it looked ugly and was too verbose.

Let's continue with the biggest pain, while I did the update on my #pelletyze codebase. ESLint v9. I was tried and erroring my way through the process but the documentation on both sides, Next.js and ESLInt were not so user-friendly then for the other codebases. Sure, if you just use ESLint, you are fine. But the combination with Next.js was a bit of a pain in the ass. Therefore, I don't like the look of the new flat config, especially the fix Next.js needed to bring in, to get everything to work. I hope this will improve in the future.

Before or after you upgrade, delete the .next folder. I had seen it much earlier to save some sanity. Because every lint run failed, and because there was some old code in it. Which gave me this error: a.getScope is not a function. After this issue was solved, I needed to add some packages because Next.js was not installing them by itself, thous it depends on them for linting. 🤷 These packages are: eslint-plugin-react and eslint-plugin-react-hook. This all happened on my second try. On the first one, a stopped after 2hrs and watched Severance. On the next day, I stared from scratch. I've installed a new test project with create-next-app and copied the important ESLint config stuff. Then I stepped my way through my old config. After I solved the major issues, the rest followed effortless. I had eslint-prettier installed, which was easy to put into the new config. And then there were some rules for next-intl, which was also just copy pasta into the new config.

Yesterday evening, I got to bed with a good feeling. Because I was done with the big update and I can now continue on my roadmap to the first public release of the #pelletyze app.

Man, what a huge update post. :) And the first one, completely written while traveling by train. I hope that someone finds it, and it will help to solve some issues on their side.

Now I've earned me some episode of Severance. 😎


13 of #100DaysToOffload
#pelletyze
Thoughts? Discuss...

Another Sunday with some time to work on my App.

Today I made good progress. Mostly for code in the background, so I don't really have screenshots besides one.


This will add the current version number to the frontend. First, I thought about a complex idea by adding the version number to an .env file or to a constant hardcoded inside the codebase. Then I thought, why not use the package.json version number? I maintain them with every release, so it is the single of truth. The code is also simple and just works.

import packageJson from "@/package.json";

export function AppVersionNumber() {
  return (
    <p className="flex gap-2 justify-center items-center">
      <small
        className="opacity-50 align-middle"
        title={`App Version: packageJson.version`}
        aria-label={`App Version: packageJson.version`}
      >
        {packageJson.version}
      </small>
    </p>
  );
}

Next on my plate, there were some bugs to fix. One, I saw at the turn of the year and I had it on my list since then. I couldn't really explain how this came together. Luckily, this one resolved itself after I removed the old data and put the new one in. Because the old data was corrupted.


Another bug was a simple fix because it was just a key prop warning. I've added the key prop and everything was fine. 😎


The last Bug was related to the data export to CSV. First I've string.replaced() my way through, but later I found out, this isn't an ideal solution. So I sat down and rewrote some parts. Supabase has its own csv export, right after you fetch your data. Which is nice, but the result was not importable for fast-csv.

Now I run csv.writeToString on my formatted data. The result cuts the file size by half. Which is not relevant because it's under 1kb but still, a good achievement.

  const formattedData = [
    ["bags", "filled_at"],
    ...(data || []).map((row) => [
      row.bags,
      format(row.filled_at, "yyyy-MM-dd"),
    ]),
  ];

  const exportReadyCSV = await csv.writeToString(formattedData, {
    delimiter: ";",
  });

And the last to-do for today was, to update all packages with the patch and minor version updates. If clicked through the application and everything worked, so this was a quick one too. :)

I would say that this was a productive Sunday. :)

I will visit my client from Tuesday to Thursday, and I'm sitting around 12hrs on the Train, so if I'm lazy, I will be watching some series or playing on my Steam Deck. OR I will be productive and work more on my Application. It depends on the mood. So lets see. :)


11 of #100DaysToOffload
#pelletyze
Thoughts? Discuss...

Finally, I was able to work again on my Pelletyze project. 🥳

The Bug

There was a bug, where the checkbox was not recognized and the formData I've sent to the server was empty. This feature worked in the past and I don't understand why it broke.

The Fix

Because I use the @radix-ui/react-checkbox and don't handle the state manually, I need to wrap with a @radix-ui/react-form. That's it. 😎

While I was working on the component, I also transformed it into a server component. Which was easily done, by using getTranslation() from next-intl instead of useTranslation and making the component asynchronous.

The bug was the reason I've implemented the delete-all Button. To save me a trip to the Supabase admin page and clear the table.

This was my Sunday contribution to Pelletyze.


10 of #100DaysToOffload
#pelletyze
Thoughts? Discuss...

This update is small. I've fixed a small bug on mobile view and updated the CHANGELOG.md, which I forgot after the last release. Now everything is as it should be.

Here, on the left is the bug and on the right the fixed version.

Sadly, I don't have time for more at the moment. But I think it is better than nothing.


07 of #100DaysToOffload
#TheMonthProject #pelletyze
Thoughts? Discuss...

Today, just a small update. Yesterday I forgot to commit my changes. While I was on, I prepared the new release, which I thought I already did it in December.

So, I just did some small clean up tasks. Updating the CHANGELOG.md, and tagging the current commit with the version number v0.5.0-alpine-shepherd-boy. Because I'm a massive fan of the series Better Call Saul, my version names will have the names of episodes from BCS.

And that's it for #TheMonthProject update #3. Furthermore, this is post 5 in the #100DaysToOffload challenge. The pressure to complete round 1 did something to me. I already have 5 posts within the first week. Which makes me happy. Seems like I'm now in some flow of writing blog posts. 🥳


05 of #100DaysToOffload
#TheMonthProject #pelletyze
Thoughts? Discuss...

Today, I've implemented a “delete all” button. To have the opportunity to just delete all entries. This is just a convenient feature so that I don't have to do it directly in Supabase. I don't know if a real user would need this, but it was easy to implement and saves me some steps. Most of the delete logic is already implemented, I just needed to generalize the button and the action. Doing Server Actions in Next.js is straightforward to implement.

The most time was used on the question, where I should put this button. While on it, I also fixed some minor style issues for desktop and mobile.

That's it for today's #TheMonthProject effort. Going +50 min on this simple topic feels a bit long. But anyway, progress is still progress. 👋


04 of #100DaysToOffload
#TheMonthProject #pelletyze
Thoughts? Discuss...

After spending some family time, the House is now relatively quiet because 50% of the Family members are sleeping. Which is a good time, to do some side-project work.

For some time, I'm working on a pellet analyze site. I collect the data, when and how many pellet bags I refill in our tank. Which will give me then an overview of how many pellets we used in the past. With this data, I can see at which time we turned on the heating in the house or have a before and after view after we did the house insulation. It's not much and I keep adding some smaller features. Currently, it will give me a number, on which I can roughly estimate how we need for the year. Because the storage is nearly empty, and I need to get more.

For the app, I've created a small script which will fetch the current price of 1t of pellets. It is running inside a cron job. And by occasionally checking the app, I can see when the price is good and should order new pellets.

Lately, I've found a bug, where the fetched price is not the same as I can see on the page of the supplier. So I've done some investigation and found out, that I missed a combination of query parameters. While I was on it, I removed some I didn't need anymore. I'm happy that the page of the supplier is stable without changing the API a lot.

That's it for today. This was my contribution to #TheMonthProject. I've done a minimal introduction of my pellet analyze side-project and fixed a long-overdue bug. 😃


02 of #100DaysToOffload
#TheMonthProject #pelletyze
Thoughts? Discuss...