Edit Readme & Clean up anything left over from the already outdated template I made

This commit is contained in:
2025-06-19 13:48:13 -05:00
parent 301a9acec0
commit 4a9c1a7fec
9 changed files with 285 additions and 113 deletions

125
README.md
View File

@ -1,19 +1,124 @@
# T3 Template with Self Hosted Supabase
<h1 align="center">
<br>
<a href="https://techtracker.gbrown.org"><img src="https://git.gbrown.org/gib/tech-tracker-next/raw/branch/master/public/favicon.png" alt="Tech Tracker Logo" width="100"></a>
<br>
<b>Tech Tracker</b>
<br>
</h1>
This is my template for self hosting both Next.js & Supabase in order to create a perfect app!!
# [Find Here](https://techtracker.gbrown.org/)
## What to do
- Application used by COG employees to update their status & location throughout the day.
- [Self Host Supabase](https://supabase.com/docs/guides/self-hosting/docker)
- You will need to make sure you have some way to connect to the postgres database from the host. I had to remove the database port from the supabase-pooler and add it to the supabase-db in order to directly connect to it. This will be important for generating our types.
- Clone this repo.
- Go to src/server/db/schema.sql & run this SQL in the SQL editor on the Web UI of your Supabase instance.
- Generate your types
- This part is potentially super weird if you are self hosting. If you are connecting directly to your database that you plan to use for production, you will need to clone your repo on the host running supabase so that you can then use the supabase cli tool. Once you have done that, you will need to install the supabase-cli tool with sudo. I just run something like `sudo npx supabase --help` and then accept the prompt to install the program. Once you have done this, you can then run the following command, replacing the password and the port to match your supabase database. You can also try running the provided script `./scripts/generate_types`
<details>
<summary>
<h2>How to run:</h2>
</summary>
### Clone the Repository & Install Dependencies
```bash
git clone https://git.gbrown.org/gib/tech-tracker-next.git
```
```bash
cd tech-tracker-next
```
I would recommend using [pnpm](https://pnpm.io/) to install dependencies.
```bash
pnpm install
```
You will also need docker installed on whatever host you plan to run the Supabase instance from, whether locally, or on a home server or a VPS or whatever. Or you can just use the Supabase SaaS if you want to have a much easier time, probably. I wouldn't know!
### Add your environment variables
Copy the example environment variable files and paste them in the same directory named `.env`.
```bash
cp ./env.example ./.env
```
```bash
cp ./src/server/docker/env.example ./src/server/docker/.env
```
Add your secrets to the `.env` files you just copied.
### Host Supabase Locally
- Follow the instructions [here](https://supabase.com/docs/guides/self-hosting/docker) to host Supabase with Docker.
- You will need to make sure you have some way to connect to the postgres database from the host. I had to remove the database port from the supabase-pooler and add it to the supabase-db in order to directly connect to it. This will be important for generating our types. This is not strictly necessary, and honestly I think I may even just have the docker compose set up to do this already, as I can't figure out why I would want to port to the spooler open on my host anyways.
### Create your database schema & generate your types.
- Copy the contents of the schema file located in `./src/server/db/schema.sql` & paste it into the SQL editor on the Web UI of your Supabase instance. Run the SQL. There should be no errors & you should now be able to see the profiles & statuses tables in the table editor.
```bash
cat ./src/server/db/schema.sql | wl-copy # If you are on Linux
```
- Generate your types.
- This can be a bit weird depending on what your setup is. If you are running Supabase locally on the same host that you are running your dev server, then this should be straightforward. If you are using the Supabase SaaS, then this is even more straightforward. If you are like me, and you are connecting to a self hosted instance of Supabase on your home server while developing, then you must clone this reposity on your server so that the command line tool can generate the types from your open postgres port on your Host, which is why the docker compose is configured how it was & why I mentioned this earlier.
You will need to run the supabase cli tool with sudo in my experience. What I would recommend to you is to run the command
```bash
sudo npx supabase --help
```
You will be prompted to install the supabase cli tool if you do not already have it installed, which you probably don't since root is running this. After that, you can run the following command below, replacing the password and the port to match your own Supabase Postgres Database port & password.
```bash
sudo npx supabase gen types typescript \
--db-url "postgres://postgres:password@localhost:5432/postgres" \
--schema public \
> ./src/lib/types
> ./src/utils/supabase/types.ts
```
There is also a script in the `scripts` folder called `generate_types` which *should* do this for you.
```bash
./scripts/generate_types
```
### Start your development environment.
Run
```bash
pnpm dev
```
to start your development environment with turbopack
You can also run
```bash
pnpm dev:slow
```
to start your development environment with webpack
### Start your Production Environment.
There are Dockerfiles & docker compose files that can be found in the `./scripts/docker` folder for the Next.js website. There is also a script called `reload_container` located in the `./scripts/` folder which was created to quickly update the container, but this will give you a better idea of what you need to do. First, build the image with
```bash
sudo docker compose -f ./scripts/docker/production/compose.yml build
```
then you can run the container with
```bash
sudo docker compose -f ./scripts/docker/production/compose up -d
```
Now, you may end up with some build errors. The `reload_containers` script swaps out the next config before it runs the docker build to skip any build errors, so you may want to do this as well, though you are welcome to fix the build errors as well, of course!
### Fin
I am sure I am missing a lot of stuff so feel free to open an issue if you have any questions or if you feel that I should add something here!
</details>

View File

@ -1,6 +1,6 @@
import { FlatCompat } from '@eslint/eslintrc';
import tseslint from 'typescript-eslint';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
//import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
const compat = new FlatCompat({
baseDirectory: import.meta.dirname,

View File

@ -45,7 +45,7 @@ const sentryConfig = {
// For all available options, see:
// https://www.npmjs.com/package/@sentry/webpack-plugin#options
org: 'gib',
project: 't3-supabase-template',
project: 'tech-tracker-next',
sentryUrl: process.env.NEXT_PUBLIC_SENTRY_URL,
authToken: process.env.SENTRY_AUTH_TOKEN,
// Only print logs for uploading source maps in CI

View File

@ -35,7 +35,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.510.0",
"next": "^15.3.3",
"next": "^15.3.4",
"next-plausible": "^3.12.4",
"next-themes": "^0.4.6",
"react": "^19.1.0",
@ -56,7 +56,7 @@
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"eslint": "^9.29.0",
"eslint-config-next": "^15.3.3",
"eslint-config-next": "^15.3.4",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-prettier": "^5.5.0",
"import-in-the-middle": "^1.14.2",

120
pnpm-lock.yaml generated
View File

@ -40,7 +40,7 @@ importers:
version: 1.2.3(@types/react@19.1.8)(react@19.1.0)
'@sentry/nextjs':
specifier: ^9.30.0
version: 9.30.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(webpack@5.99.9)
version: 9.30.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@15.3.4(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(webpack@5.99.9)
'@supabase/ssr':
specifier: ^0.6.1
version: 0.6.1(@supabase/supabase-js@2.50.0)
@ -63,11 +63,11 @@ importers:
specifier: ^0.510.0
version: 0.510.0(react@19.1.0)
next:
specifier: ^15.3.3
version: 15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
specifier: ^15.3.4
version: 15.3.4(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
next-plausible:
specifier: ^3.12.4
version: 3.12.4(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
version: 3.12.4(next@15.3.4(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
next-themes:
specifier: ^0.4.6
version: 0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@ -121,8 +121,8 @@ importers:
specifier: ^9.29.0
version: 9.29.0(jiti@2.4.2)
eslint-config-next:
specifier: ^15.3.3
version: 15.3.3(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)
specifier: ^15.3.4
version: 15.3.4(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)
eslint-config-prettier:
specifier: ^10.1.5
version: 10.1.5(eslint@9.29.0(jiti@2.4.2))
@ -468,56 +468,56 @@ packages:
'@napi-rs/wasm-runtime@0.2.11':
resolution: {integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==}
'@next/env@15.3.3':
resolution: {integrity: sha512-OdiMrzCl2Xi0VTjiQQUK0Xh7bJHnOuET2s+3V+Y40WJBAXrJeGA3f+I8MZJ/YQ3mVGi5XGR1L66oFlgqXhQ4Vw==}
'@next/env@15.3.4':
resolution: {integrity: sha512-ZkdYzBseS6UjYzz6ylVKPOK+//zLWvD6Ta+vpoye8cW11AjiQjGYVibF0xuvT4L0iJfAPfZLFidaEzAOywyOAQ==}
'@next/eslint-plugin-next@15.3.3':
resolution: {integrity: sha512-VKZJEiEdpKkfBmcokGjHu0vGDG+8CehGs90tBEy/IDoDDKGngeyIStt2MmE5FYNyU9BhgR7tybNWTAJY/30u+Q==}
'@next/eslint-plugin-next@15.3.4':
resolution: {integrity: sha512-lBxYdj7TI8phbJcLSAqDt57nIcobEign5NYIKCiy0hXQhrUbTqLqOaSDi568U6vFg4hJfBdZYsG4iP/uKhCqgg==}
'@next/swc-darwin-arm64@15.3.3':
resolution: {integrity: sha512-WRJERLuH+O3oYB4yZNVahSVFmtxRNjNF1I1c34tYMoJb0Pve+7/RaLAJJizyYiFhjYNGHRAE1Ri2Fd23zgDqhg==}
'@next/swc-darwin-arm64@15.3.4':
resolution: {integrity: sha512-z0qIYTONmPRbwHWvpyrFXJd5F9YWLCsw3Sjrzj2ZvMYy9NPQMPZ1NjOJh4ojr4oQzcGYwgJKfidzehaNa1BpEg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
'@next/swc-darwin-x64@15.3.3':
resolution: {integrity: sha512-XHdzH/yBc55lu78k/XwtuFR/ZXUTcflpRXcsu0nKmF45U96jt1tsOZhVrn5YH+paw66zOANpOnFQ9i6/j+UYvw==}
'@next/swc-darwin-x64@15.3.4':
resolution: {integrity: sha512-Z0FYJM8lritw5Wq+vpHYuCIzIlEMjewG2aRkc3Hi2rcbULknYL/xqfpBL23jQnCSrDUGAo/AEv0Z+s2bff9Zkw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
'@next/swc-linux-arm64-gnu@15.3.3':
resolution: {integrity: sha512-VZ3sYL2LXB8znNGcjhocikEkag/8xiLgnvQts41tq6i+wql63SMS1Q6N8RVXHw5pEUjiof+II3HkDd7GFcgkzw==}
'@next/swc-linux-arm64-gnu@15.3.4':
resolution: {integrity: sha512-l8ZQOCCg7adwmsnFm8m5q9eIPAHdaB2F3cxhufYtVo84pymwKuWfpYTKcUiFcutJdp9xGHC+F1Uq3xnFU1B/7g==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@next/swc-linux-arm64-musl@15.3.3':
resolution: {integrity: sha512-h6Y1fLU4RWAp1HPNJWDYBQ+e3G7sLckyBXhmH9ajn8l/RSMnhbuPBV/fXmy3muMcVwoJdHL+UtzRzs0nXOf9SA==}
'@next/swc-linux-arm64-musl@15.3.4':
resolution: {integrity: sha512-wFyZ7X470YJQtpKot4xCY3gpdn8lE9nTlldG07/kJYexCUpX1piX+MBfZdvulo+t1yADFVEuzFfVHfklfEx8kw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@next/swc-linux-x64-gnu@15.3.3':
resolution: {integrity: sha512-jJ8HRiF3N8Zw6hGlytCj5BiHyG/K+fnTKVDEKvUCyiQ/0r5tgwO7OgaRiOjjRoIx2vwLR+Rz8hQoPrnmFbJdfw==}
'@next/swc-linux-x64-gnu@15.3.4':
resolution: {integrity: sha512-gEbH9rv9o7I12qPyvZNVTyP/PWKqOp8clvnoYZQiX800KkqsaJZuOXkWgMa7ANCCh/oEN2ZQheh3yH8/kWPSEg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@next/swc-linux-x64-musl@15.3.3':
resolution: {integrity: sha512-HrUcTr4N+RgiiGn3jjeT6Oo208UT/7BuTr7K0mdKRBtTbT4v9zJqCDKO97DUqqoBK1qyzP1RwvrWTvU6EPh/Cw==}
'@next/swc-linux-x64-musl@15.3.4':
resolution: {integrity: sha512-Cf8sr0ufuC/nu/yQ76AnarbSAXcwG/wj+1xFPNbyNo8ltA6kw5d5YqO8kQuwVIxk13SBdtgXrNyom3ZosHAy4A==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@next/swc-win32-arm64-msvc@15.3.3':
resolution: {integrity: sha512-SxorONgi6K7ZUysMtRF3mIeHC5aA3IQLmKFQzU0OuhuUYwpOBc1ypaLJLP5Bf3M9k53KUUUj4vTPwzGvl/NwlQ==}
'@next/swc-win32-arm64-msvc@15.3.4':
resolution: {integrity: sha512-ay5+qADDN3rwRbRpEhTOreOn1OyJIXS60tg9WMYTWCy3fB6rGoyjLVxc4dR9PYjEdR2iDYsaF5h03NA+XuYPQQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
'@next/swc-win32-x64-msvc@15.3.3':
resolution: {integrity: sha512-4QZG6F8enl9/S2+yIiOiju0iCTFd93d8VC1q9LZS4p/Xuk81W2QDjCFeoogmrWWkAD59z8ZxepBQap2dKS5ruw==}
'@next/swc-win32-x64-msvc@15.3.4':
resolution: {integrity: sha512-4kDt31Bc9DGyYs41FTL1/kNpDeHyha2TC0j5sRRoKCyrhNcfZ/nRQkAUlF27mETwm8QyHqIjHJitfcza2Iykfg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
@ -2144,8 +2144,8 @@ packages:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
eslint-config-next@15.3.3:
resolution: {integrity: sha512-QJLv/Ouk2vZnxL4b67njJwTLjTf7uZRltI0LL4GERYR4qMF5z08+gxkfODAeaK7TiC6o+cER91bDaEnwrTWV6Q==}
eslint-config-next@15.3.4:
resolution: {integrity: sha512-WqeumCq57QcTP2lYlV6BRUySfGiBYEXlQ1L0mQ+u4N4X4ZhUVSSQ52WtjqHv60pJ6dD7jn+YZc0d1/ZSsxccvg==}
peerDependencies:
eslint: ^7.23.0 || ^8.0.0 || ^9.0.0
typescript: '>=3.3.1'
@ -2861,8 +2861,8 @@ packages:
react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
next@15.3.3:
resolution: {integrity: sha512-JqNj29hHNmCLtNvd090SyRbXJiivQ+58XjCcrC50Crb5g5u2zi7Y2YivbsEfzk6AtVI80akdOQbaMZwWB1Hthw==}
next@15.3.4:
resolution: {integrity: sha512-mHKd50C+mCjam/gcnwqL1T1vPx/XQNFlXqFIVdgQdVAFY9iIQtY0IfaVflEYzKiqjeA7B0cYYMaCrmAYFjs4rA==}
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
hasBin: true
peerDependencies:
@ -2969,8 +2969,8 @@ packages:
resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
engines: {node: '>=4.0.0'}
pg-protocol@1.10.0:
resolution: {integrity: sha512-IpdytjudNuLv8nhlHs/UrVBhU0e78J0oIS/0AVdTbWxSOkFUVdsHC/NrorO6nXsQNDTT1kzDSOMJubBQviX18Q==}
pg-protocol@1.10.1:
resolution: {integrity: sha512-9YS3ZonDj0Lxny//aF0ITPdfrEPgKWCJvONsSXAaIUhgpzlzl5JgaZNlbTFxvYNfm2terGEnHeOSUlF6qRGBzw==}
pg-types@2.2.0:
resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
@ -3936,34 +3936,34 @@ snapshots:
'@tybys/wasm-util': 0.9.0
optional: true
'@next/env@15.3.3': {}
'@next/env@15.3.4': {}
'@next/eslint-plugin-next@15.3.3':
'@next/eslint-plugin-next@15.3.4':
dependencies:
fast-glob: 3.3.1
'@next/swc-darwin-arm64@15.3.3':
'@next/swc-darwin-arm64@15.3.4':
optional: true
'@next/swc-darwin-x64@15.3.3':
'@next/swc-darwin-x64@15.3.4':
optional: true
'@next/swc-linux-arm64-gnu@15.3.3':
'@next/swc-linux-arm64-gnu@15.3.4':
optional: true
'@next/swc-linux-arm64-musl@15.3.3':
'@next/swc-linux-arm64-musl@15.3.4':
optional: true
'@next/swc-linux-x64-gnu@15.3.3':
'@next/swc-linux-x64-gnu@15.3.4':
optional: true
'@next/swc-linux-x64-musl@15.3.3':
'@next/swc-linux-x64-musl@15.3.4':
optional: true
'@next/swc-win32-arm64-msvc@15.3.3':
'@next/swc-win32-arm64-msvc@15.3.4':
optional: true
'@next/swc-win32-x64-msvc@15.3.3':
'@next/swc-win32-x64-msvc@15.3.4':
optional: true
'@nodelib/fs.scandir@2.1.5':
@ -4747,7 +4747,7 @@ snapshots:
'@sentry/core@9.30.0': {}
'@sentry/nextjs@9.30.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(webpack@5.99.9)':
'@sentry/nextjs@9.30.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@15.3.4(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(webpack@5.99.9)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/semantic-conventions': 1.34.0
@ -4760,7 +4760,7 @@ snapshots:
'@sentry/vercel-edge': 9.30.0
'@sentry/webpack-plugin': 3.5.0(webpack@5.99.9)
chalk: 3.0.0
next: 15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
next: 15.3.4(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
resolve: 1.22.8
rollup: 4.35.0
stacktrace-parser: 0.1.11
@ -5067,7 +5067,7 @@ snapshots:
'@types/pg@8.6.1':
dependencies:
'@types/node': 20.19.1
pg-protocol: 1.10.0
pg-protocol: 1.10.1
pg-types: 2.2.0
'@types/phoenix@1.6.6': {}
@ -5768,9 +5768,9 @@ snapshots:
escape-string-regexp@4.0.0: {}
eslint-config-next@15.3.3(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3):
eslint-config-next@15.3.4(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3):
dependencies:
'@next/eslint-plugin-next': 15.3.3
'@next/eslint-plugin-next': 15.3.4
'@rushstack/eslint-patch': 1.11.0
'@typescript-eslint/eslint-plugin': 8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)
'@typescript-eslint/parser': 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)
@ -6506,9 +6506,9 @@ snapshots:
neo-async@2.6.2: {}
next-plausible@3.12.4(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
next-plausible@3.12.4(next@15.3.4(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
next: 15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
next: 15.3.4(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
@ -6517,9 +6517,9 @@ snapshots:
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
next@15.3.4(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
'@next/env': 15.3.3
'@next/env': 15.3.4
'@swc/counter': 0.1.3
'@swc/helpers': 0.5.15
busboy: 1.6.0
@ -6529,14 +6529,14 @@ snapshots:
react-dom: 19.1.0(react@19.1.0)
styled-jsx: 5.1.6(@babel/core@7.27.4)(react@19.1.0)
optionalDependencies:
'@next/swc-darwin-arm64': 15.3.3
'@next/swc-darwin-x64': 15.3.3
'@next/swc-linux-arm64-gnu': 15.3.3
'@next/swc-linux-arm64-musl': 15.3.3
'@next/swc-linux-x64-gnu': 15.3.3
'@next/swc-linux-x64-musl': 15.3.3
'@next/swc-win32-arm64-msvc': 15.3.3
'@next/swc-win32-x64-msvc': 15.3.3
'@next/swc-darwin-arm64': 15.3.4
'@next/swc-darwin-x64': 15.3.4
'@next/swc-linux-arm64-gnu': 15.3.4
'@next/swc-linux-arm64-musl': 15.3.4
'@next/swc-linux-x64-gnu': 15.3.4
'@next/swc-linux-x64-musl': 15.3.4
'@next/swc-win32-arm64-msvc': 15.3.4
'@next/swc-win32-x64-msvc': 15.3.4
'@opentelemetry/api': 1.9.0
sharp: 0.34.2
transitivePeerDependencies:
@ -6633,7 +6633,7 @@ snapshots:
pg-int8@1.0.1: {}
pg-protocol@1.10.0: {}
pg-protocol@1.10.1: {}
pg-types@2.2.0:
dependencies:

View File

@ -1,15 +1,9 @@
// This file configures the initialization of Sentry on the server.
// The config you add here will be used whenever the server handles a request.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from '@sentry/nextjs';
import './src/env.js';
Sentry.init({
dsn: 'https://0468176d5291bc2b914261147bfef117@sentry.gbrown.org/6',
// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
tracesSampleRate: 1,
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
tracesSampleRate: 1, // Define how likely traces are sampled or use tracesSampler for more control.
debug: false, // Print useful debugging info while setting up Sentry.
});

View File

@ -50,15 +50,6 @@ export const StatusList = ({ initialStatuses = [] }: ListProps) => {
});
});
//const { connectionStatus, connect: reconnect } = useStatusSubscription({
//enabled: isAuthenticated,
//onStatusUpdate: () => {
//refetch().catch((error) => {
//console.error('Error refetching statuses:', error);
//});
//},
//});
const handleUpdateStatus = () => {
if (!isAuthenticated) {
setUpdateStatusMessage(
@ -306,7 +297,7 @@ export const StatusList = ({ initialStatuses = [] }: ListProps) => {
)}
{!tvMode && (
<Card className='p-6 mt-6'>
<Card className='p-6 mt-4 w-full'>
<div className='flex flex-col gap-4'>
<h3 className='text-lg font-semibold'>Update Status</h3>
<div className='flex flex-col gap-4'>

View File

@ -1,5 +1,5 @@
'use client';
import { useState, useEffect, useCallback } from 'react';
import { useState, useEffect, useCallback, useRef } from 'react';
import { createClient } from '@/utils/supabase';
import type { RealtimeChannel } from '@supabase/supabase-js';
@ -14,115 +14,197 @@ let sharedChannel: RealtimeChannel | null = null;
let sharedConnectionStatus: ConnectionStatus = 'disconnected';
const subscribers = new Set<(status: ConnectionStatus) => void>();
const statusUpdateCallbacks = new Set<() => void>();
//const subscribers: Set<(status: ConnectionStatus) => void> = new Set();
//const statusUpdateCallbacks: Set<() => void> = new Set();
let reconnectAttempts = 0;
let reconnectTimeout: NodeJS.Timeout | undefined;
const supabase = createClient();
const notifySubscribers = (status: ConnectionStatus) => {
console.log('📢 notifySubscribers: Notifying', subscribers.size, 'subscribers of status change to:', status);
sharedConnectionStatus = status;
subscribers.forEach(callback => callback(status));
subscribers.forEach((callback, index) => {
console.log('📢 notifySubscribers: Calling subscriber', index + 1);
callback(status);
});
console.log('📢 notifySubscribers: All subscribers notified');
};
const notifyStatusUpdate = () => {
statusUpdateCallbacks.forEach(callback => callback());
console.log('🔄 notifyStatusUpdate: Notifying', statusUpdateCallbacks.size, 'status update callbacks');
statusUpdateCallbacks.forEach((callback, index) => {
console.log('🔄 notifyStatusUpdate: Calling callback', index + 1);
callback();
});
console.log('🔄 notifyStatusUpdate: All callbacks executed');
};
const cleanup = () => {
console.log('🧹 cleanup: Starting cleanup process');
if (reconnectTimeout) {
console.log('🧹 cleanup: Clearing reconnect timeout');
clearTimeout(reconnectTimeout);
reconnectTimeout = undefined;
}
if (sharedChannel) {
console.log('🧹 cleanup: Removing shared channel');
supabase.removeChannel(sharedChannel).catch((error) => {
console.error('Error removing shared channel:', error);
console.error('❌ cleanup: Error removing shared channel:', error);
});
sharedChannel = null;
}
console.log('✅ cleanup: Cleanup completed');
};
const connect = () => {
if (sharedChannel) return; // Already connected or connecting
console.log('🔌 connect: Function called');
console.log('🔌 connect: sharedChannel exists:', !!sharedChannel);
console.log('🔌 connect: subscribers count:', subscribers.size);
if (sharedChannel) {
console.log('❌ connect: Already connected or connecting, returning early');
return;
}
console.log('🔌 connect: Starting connection process');
cleanup();
notifySubscribers('connecting');
console.log('🔌 connect: Creating new channel');
const channel = supabase
.channel('shared_status_updates', {
config: { broadcast: {self: true }}
})
.on('broadcast', { event: 'status_updated' }, () => {
.on('broadcast', { event: 'status_updated' }, (payload) => {
console.log('📡 connect: Broadcast event received:', payload);
notifyStatusUpdate();
})
.subscribe((status) => {
console.log('📡 connect: Subscription status changed to:', status);
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
if (status === 'SUBSCRIBED') {
console.log('✅ connect: Successfully subscribed to realtime');
notifySubscribers('connected');
reconnectAttempts = 0;
console.log('✅ connect: Reset reconnect attempts to 0');
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
} else if (status === 'CHANNEL_ERROR' || status === 'CLOSED') {
console.log('❌ connect: Channel error or closed, status:', status);
notifySubscribers('disconnected');
if (reconnectAttempts < 5) {
reconnectAttempts++;
const delay = 2000 * reconnectAttempts;
console.log('🔄 connect: Scheduling reconnection attempt', reconnectAttempts, 'in', delay, 'ms');
reconnectTimeout = setTimeout(() => {
if (subscribers.size > 0) { // Only reconnect if there are active subscribers
console.log('🔄 connect: Reconnection timeout executed');
if (subscribers.size > 0) {
console.log('🔄 connect: Calling connect() for reconnection');
connect();
} else {
console.log('❌ connect: No active subscribers, skipping reconnection');
}
}, delay);
} else {
console.warn('⚠️ connect: Max reconnection attempts (5) reached');
}
}
});
sharedChannel = channel;
console.log('🔌 connect: Channel stored in sharedChannel variable');
};
const disconnect = () => {
console.log('🔌 disconnect: Function called');
cleanup();
notifySubscribers('disconnected');
};
export const useSharedStatusSubscription = (onStatusUpdate?: () => void) => {
console.log('🚀 useSharedStatusSubscription: Hook called');
const [connectionStatus, setConnectionStatus] = useState<ConnectionStatus>(sharedConnectionStatus);
const onStatusUpdateRef = useRef(onStatusUpdate);
const hasInitialized = useRef(false);
// Keep the ref updated
onStatusUpdateRef.current = onStatusUpdate;
// Create a stable callback
const stableOnStatusUpdate = useCallback(() => {
onStatusUpdateRef.current?.();
}, []);
useEffect(() => {
console.log('🔧 useSharedStatusSubscription useEffect: Running');
console.log('🔧 useSharedStatusSubscription useEffect: hasInitialized:', hasInitialized.current);
console.log('🔧 useSharedStatusSubscription useEffect: Current subscribers count:', subscribers.size);
// Prevent duplicate initialization
if (hasInitialized.current) {
console.log('🔧 useSharedStatusSubscription useEffect: Already initialized, skipping');
return;
}
hasInitialized.current = true;
// Subscribe to status changes
subscribers.add(setConnectionStatus);
console.log('🔧 useSharedStatusSubscription useEffect: Added setConnectionStatus to subscribers');
// Subscribe to status updates
if (onStatusUpdate) {
statusUpdateCallbacks.add(onStatusUpdate);
statusUpdateCallbacks.add(stableOnStatusUpdate);
console.log('🔧 useSharedStatusSubscription useEffect: Added stable onStatusUpdate callback');
}
// Connect if this is the first subscriber
if (subscribers.size === 1) {
const timeout = setTimeout(connect, 1000);
return () => clearTimeout(timeout);
console.log('🔧 useSharedStatusSubscription useEffect: First subscriber, setting up connection');
const timeout = setTimeout(() => {
console.log('🔧 useSharedStatusSubscription useEffect: Connection timeout executed, calling connect()');
connect();
}, 1000);
return () => {
console.log('🔧 useSharedStatusSubscription useEffect: Cleanup - clearing connection timeout');
clearTimeout(timeout);
hasInitialized.current = false;
subscribers.delete(setConnectionStatus);
statusUpdateCallbacks.delete(stableOnStatusUpdate);
if (subscribers.size === 0) {
disconnect();
}
};
}
return () => {
// Cleanup subscriptions
console.log('🔧 useSharedStatusSubscription useEffect: Cleanup function running');
hasInitialized.current = false;
subscribers.delete(setConnectionStatus);
if (onStatusUpdate) {
statusUpdateCallbacks.delete(onStatusUpdate);
}
statusUpdateCallbacks.delete(stableOnStatusUpdate);
// Disconnect if no more subscribers
if (subscribers.size === 0) {
console.log('🔧 useSharedStatusSubscription useEffect: No more subscribers, calling disconnect()');
disconnect();
}
};
}, [onStatusUpdate]);
}, []); // Empty dependency array!
const reconnect = useCallback(() => {
console.log('🔄 reconnect: Function called');
reconnectAttempts = 0;
console.log('🔄 reconnect: Reset reconnectAttempts to 0, calling connect()');
connect();
}, []);
console.log('🏁 useSharedStatusSubscription: connectionStatus:', connectionStatus);
return {
connectionStatus,
connect: reconnect,

View File

@ -37,7 +37,7 @@ export const useStatusData = ({
}
},
enabled,
refetchInterval: 30000,
refetchInterval: 30000, // 30 seconds
refetchOnWindowFocus: true,
refetchOnMount: true,
initialData,