diff --git a/.env.example b/.env.example index 75c06f24..5f19152e 100644 --- a/.env.example +++ b/.env.example @@ -1,156 +1,199 @@ -#=========================================== -# 🔴 REQUIRED -#=========================================== +# || 🔥 [Important note]: Please note that currently, v1.2.6 requires specifying Clerk, as its API has changed. +# || We are working on making Clerk optional again. However, all other environment variables are optional. +# || If this statement is incorrect, meaning something is broken somewhere, please let us know. +# || https://github.com/blefnk/relivator-nextjs-template -# planetscale (https://planetscale.com) | neon (https://neon.tech) -# vercel (https://vercel.com) | railway (https://railway.app) -NEXT_PUBLIC_DB_PROVIDER="planetscale" +# ==================================================== +# GENERAL +# ==================================================== -# mysql://username:password@hostname/database -# postgres://username:password@hostname/database -DATABASE_URL="mysql://YOUR_MYSQL_URL_HERE" +# || You can try our brand-new linting script: `pnpm lint:env` or `pnpm appts:env`. +# || These commands will check the correctness of your .env and .env.example files. -#=========================================== -# 🟢 OPTIONAL (EASY) -#=========================================== - -# clerk (https://clerk.com) | authjs (https://authjs.dev) -NEXT_PUBLIC_AUTH_PROVIDER="authjs" +# Specify the website domain in production +NEXT_PUBLIC_APP_URL="http://localhost:3000" -# Required if you have chosen "clerk" as auth provider -# https://dashboard.clerk.com/last-active?path=api-keys +# ==================================================== +# DATABASE +# ==================================================== + +# || When the following connection string is set, you can run "pnpm db:push" to create/update the database tables. +# || If you've just created the database, please give your provider a moment for the database to be fully created. + +# Database (https://neon.tech) (it's recommended to check 'Pooled connection' to get the production URL) +DATABASE_URL="" + +# || DATABASE URL EXAMPLES (pg: try 'postgresql://' if 'postgres://' does not work) +# || --------------------------------------------------------------- +# || - Postgres ➞ Neon ➞ postgresql://database_owner:password@hostname/database?sslmode=require +# || - Postgres ➞ Planetscale ➞ mysql://username:password@hostname/database?ssl={"rejectUnauthorized":true} +# || - Postgres ➞ Private ➞ postgres://username:password@127.0.0.1:5432/db +# || - Postgres ➞ Railway ➞ postgres://root:password@hostname:36906/railway +# || - MySQL ➞ Railway ➞ mysql://root:password@hostname:36906/railway +# || - MySQL ➞ Private ➞ mysql://username:password@hostname/database +# || - SQLite ➞ SQLite ➞ db.sqlite +# || --------------------------------------------------------------- + +# || We are using Drizzle and Neon as default database provider +# || https://orm.drizzle.team/learn/tutorials/drizzle-with-neon + +# || NOTE: NEXT_PUBLIC_DB_PROVIDER was removed in Relivator 1.2.6 +# || To switch the provider from Neon, modify drizzle.config.ts +# || To use MySQL or LibSQL providers, update files inside src/db. +# || Automatic switcher coming in Relivator 1.3.x version. + +# ==================================================== +# AUTHENTICATION +# ==================================================== + +# || Please visit `reliverse.config.ts` and set the `authProvider` +# || to either "clerk" or "authjs". Fake session data will be assigned +# || to your users if DATABASE_URL or authProvider are not set. + +# Required for both "authjs" and "clerk" authProviders. +# https://authjs.dev/guides/environment-variables +# Recommended (bash): openssl rand -base64 33 +# Or try this: pnpm dlx randomstring length=44 +AUTH_SECRET="EnsureUseSomethingRandomHere44CharactersLong" + +# Required if you choose "authjs" as your authentication provider. +# Discord: https://discord.com/developers/applications +AUTH_DISCORD_SECRET="" +AUTH_DISCORD_ID="" +# GitHub: https://github.com/settings/developers +AUTH_GITHUB_SECRET="" +AUTH_GITHUB_ID="" +# Google: https://console.cloud.google.com/apis/credentials +AUTH_GOOGLE_SECRET="" +AUTH_GOOGLE_ID="" + +# Required if you choose "clerk" as your authentication provider. +# Obtain keys from: https://dashboard.clerk.com/last-active?path=api-keys +# Ensure the domain is connected in production (for PageSpeed Insights). NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="" CLERK_SECRET_KEY="" +# NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL="/dashboard" +# NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL="/dashboard" +NEXT_PUBLIC_CLERK_SIGN_IN_URL="/sign-in" +NEXT_PUBLIC_CLERK_SIGN_UP_URL="/sign-up" +# Additional optional feature, to enable visit: +# Clerk Dashboard > [app] > Organizations Settings +NEXT_PUBLIC_ORGANIZATIONS_ENABLED="false" + +# || NOTE: NEXT_PUBLIC_AUTH_PROVIDER was removed in Relivator 1.2.6 +# || To switch the provider from Neon, modify `reliverse.config.ts` +# || Automatic switcher coming in Relivator 1.3.x version. + +# ==================================================== +# PAYMENT SYSTEM +# ==================================================== + +# || Fake store data will be generated if DATABASE_URL +# || and STRIPE_WEBHOOK_SIGNING_SECRET are not set. + +# For API keys: https://dashboard.stripe.com/test/apikeys +NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="" +STRIPE_SECRET_KEY="" -# Specify your website domain in production -NEXT_PUBLIC_APP_URL="http://localhost:3000" - -#=========================================== -# 🟡 OPTIONAL (MEDIUM) -#=========================================== +# Please read the instructions at the end of the file. +STRIPE_WEBHOOK_SIGNING_SECRET="" -# https://dashboard.stripe.com/test/products +# For product setup: https://dashboard.stripe.com/test/products STRIPE_PROFESSIONAL_SUBSCRIPTION_PRICE_ID="" STRIPE_ENTERPRISE_SUBSCRIPTION_PRICE_ID="" -# https://dashboard.stripe.com/test/apikeys -NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="" -STRIPE_SECRET_KEY="" +# ==================================================== +# RELIVERSE ADDONS +# ==================================================== -# Read the instructions at the end of file -STRIPE_WEBHOOK_SIGNING_SECRET="" +# || Currently, "next dev --turbo" does not read the .env file after launch. +# || So, you need to close the application using Cmd/Ctrl+C and run it again. + +# Set to true if you want to enable addons/reliverse/relimter/python/index.ts +# Note: addons/reliverse/relimter/python/index.ts is a more stable version. +PYTHON_INSTALLED="false" + +# Set to true if you want to enable addons/reliverse/toolbar/index.ts toolbar +# https://vercel.com/docs/workflow-collaboration/vercel-toolbar +ENABLE_VERCEL_TOOLBAR="false" +ENABLE_VT_ON_PRODUCTION="false" + +# ENABLE_VERCEL_TOOLBAR must be enabled to enable the following +# https://vercel.com/docs/workflow-collaboration/feature-flags +ENABLE_FEATURE_FLAGS="false" +# node -e "console.log(crypto.randomBytes(32).toString('base64url'))" +FLAGS_SECRET="" + +# Remotion GitHub Personal Access Token +# Obtain from: https://github.com/settings/personal-access-tokens/new +REMOTION_GITHUB_TOKEN="" -# Specify if you want image uploads -# https://uploadthing.com/dashboard +# ==================================================== +# MEDIA UPLOAD +# ==================================================== + +# || Uploadthing is free, but redirects you to Stripe +# || after signing up, so you can just close the tab. + +# Image Upload Configuration +# https://uploadthing.com (Dashboard > [App] > API Keys) UPLOADTHING_SECRET="" UPLOADTHING_APP_ID="" -# Specify if you want email system +# ==================================================== +# EMAIL SYSTEM +# ==================================================== + +# || The email system is already partially integrated into Relivator 1.2.6. It will +# || be fully functional starting from Relivator 1.3.0. Contributions are welcome! + +# Email System Configuration # Get API keys: https://resend.com -RESEND_API_KEY="" -# Set email: https://resend.com/domains -# Or use Resend's special testing email -EMAIL_FROM_ADDRESS="onboarding@resend.dev" - -#=========================================== -# 🟠 OPTIONAL (ADVANCED) -#=========================================== - -# Specify if you have "authjs" as auth provider -# https://discord.com/developers/applications -DISCORD_CLIENT_SECRET="" -DISCORD_CLIENT_ID="" -# https://github.com/settings/developers -GITHUB_CLIENT_SECRET="" -GITHUB_CLIENT_ID="" -# https://console.cloud.google.com/apis/credentials -GOOGLE_CLIENT_SECRET="" -GOOGLE_CLIENT_ID="" -# https://generate-secret.vercel.app/32 -NEXTAUTH_SECRET="UseSomethingRandomHere32CharLong" -NEXTAUTH_URL="http://localhost:3000" - -# Specify if you want to have -# https://loglib.io analytics -LOGLIB_SITE_ID="" - -#=========================================== -# 🔵 OPTIONAL (ADDITIONAL) -#=========================================== - -# Specify if you want to enable ip rate limit -# Upstash Redis (https://console.upstash.com) -UPSTASH_REDIS_REST_URL="" -UPSTASH_REDIS_REST_TOKEN="" - -# If you want multiplayer https://liveblocks.io -NEXT_PUBLIC_LIVEBLOCKS_PUBLIC_KEY="" - -# Set "true" to enable checks for any bot activity -CHECK_BOT_ACTIVITY="false" - -# Set "true" to enable "src/core/cors/csp.mjs" file -NEXT_PUBLIC_CSP_XSS="false" - -# Set "false" to ask search engines not to index site -# Ypu need to have NEXT_PUBLIC_CSP_XSS="true" as well -NEXT_PUBLIC_IS_LIVE="true" - -# next-intl | next-international (soon) | none (soon) -NEXT_PUBLIC_INTL_PROVIDER="next-intl" - -# Set "true" to hide environment info when missing -NEXT_PUBLIC_HIDE_ENV_INFO="false" - -# It's recommended to specify your live domain -# here on the development (with https:// part) -# Use comma without spaces to specify multiple -# Ypu need to have NEXT_PUBLIC_CSP_XSS="true" -ADDITIONAL_CSP_ORIGINS="https://*.vercel.app,https://relivator.bleverse.com" - -#=========================================== -# 🟣 INSTRUCTIONS -#=========================================== +NEXT_PUBLIC_RESEND_API_KEY="" -# [STRIPE WEBHOOK FOR DEVELOPMENT] -# 1. Install Stripe CLI: https://stripe.com/docs/stripe-cli#install -# 2. https://dashboard.stripe.com/test/webhooks/create?endpoint_location=local -# 3. Open 3 terminals: (1) "pnpm dev"; (2) "stripe login"; (3) "pnpm stripe:listen". -# 4. Copy signing secret from your terminal, paste to STRIPE_WEBHOOK_SIGNING_SECRET. -# 5. Run "stripe trigger payment_intent.succeeded", wait for Completed, click Done. -# Keep "pnpm stripe:listen" enabled when you need to test Stripe on the localhost. -# When testing the Stripe -> use these test data: 4242424242424242 | 12/34 | 567 +# Set email: https://resend.com/domains or use Resend's test email +NEXT_PUBLIC_RESEND_EMAIL_FROM="onboarding@resend.dev" -# [STRIPE WEBHOOK FOR PRODUCTION] -# 1. https://dashboard.stripe.com/test/webhooks/create?endpoint_location=hosted -# 2. As endpoint use: https://use-your-domain-here.com/api/webhooks/stripe -# 3. "Select events" > "Select all events" > "Add events". -# 4. "Events on your account"; Version "Latest API version". -# 5. Scroll the page down to the end and click "Add endpoint". -# 6. Open newly created webhook and reveal your signing secret. -# Please note: you will get the test-mode production signing key, -# switch to the live-mode to get real one, steps possibly the same. - -# [IMPORTANT THINGS TO KNOW] -# Never share or commit the ".env" file anywhere. -# When adding new variables, update the schema -# in the "/src/env.mjs" file accordingly. -# Also, specify any missing variables -# in "/src/indicators-error.tsx". - -#=========================================== -# ⭕ CURRENTLY NOT USED -#=========================================== - -# For Discord Server Integration: Go to Edit Channel -# > Integrations > New Webhook, and obtain your URL +# ==================================================== +# ADDITIONAL +# ==================================================== + +# || Never share or commit the .env file. It has been added to .gitignore. +# || When adding new variables, update the schema in the /src/env.js file. + +# Loglib Analytics (https://loglib.io) +LOGLIB_ID="" + +# Discord Server Notifications Integration +# Open your server settings > Integrations > New Webhook > obtain the URL. DISCORD_WEBHOOK_URL="" -# stripe | lemonsqueezy (soon) | disable (soon) -NEXT_PUBLIC_PAYMENT_PROVIDER="stripe" +# ==================================================== +# STRIPE INSTRUCTIONS +# ==================================================== -# drizzle | prisma (soon) -NEXT_PUBLIC_DB_LIBRARY="drizzle" +# Ensure that you have the required configuration +# set up before following the instructions below: +# - DATABASE_URL +# - STRIPE_SECRET_KEY +# - NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY -# none | contentlayer (soon) -NEXT_PUBLIC_CMS_PROVIDER="none" +# [STRIPE WEBHOOK FOR DEVELOPMENT] +# 1. Install Stripe CLI: https://stripe.com/docs/stripe-cli#install +# 2. Create webhook: https://dashboard.stripe.com/test/webhooks/create?endpoint_location=local +# 3. Open 3 terminals: +# - Terminal 1: "pnpm dev" +# - Terminal 2: "stripe login" +# - Terminal 3: "pnpm stripe:listen" +# 4. Copy the signing secret from the terminal and paste it into STRIPE_WEBHOOK_SIGNING_SECRET. +# 5. Run "stripe trigger payment_intent.succeeded", wait for it to complete, then click Done. +# Keep "pnpm stripe:listen" enabled when testing Stripe on localhost. +# Test data: 4242424242424242 | 12/34 | 567 + +# [STRIPE WEBHOOK FOR PRODUCTION] +# 1. Create webhook: https://dashboard.stripe.com/test/webhooks/create?endpoint_location=hosted +# 2. Endpoint: https://use-the-domain-here.com/api/webhooks/stripe +# 3. Select all events and add the endpoint. +# 4. Ensure "Latest API version" is selected. +# 5. Reveal the signing secret. +# Note: You will get the test-mode production signing key. Switch to live-mode for the real key. diff --git a/.gitattributes b/.gitattributes index 83a29eba..fd072957 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,219 @@ -# @see https://dev.to/deadlybyte/please-add-gitattributes-to-your-git-repository-1jld -# @see https://rehansaeed.com/gitattributes-best-practices -# @see https://github.com/gitattributes/gitattributes +# GITATTRIBUTES FOR WEB PROJECTS +# +# These settings are for any web project. +# Details per file setting: +# text These files should be normalized (i.e. convert CRLF to LF). +# binary These files are binary and should be left untouched. +# +# https://rehansaeed.com/gitattributes-best-practices +# https://github.com/gitattributes/gitattributes/blob/master/Web.gitattributes +# https://dev.to/deadlybyte/please-add-gitattributes-to-your-git-repository-1jld +################################################################################ -# Auto detect text files and perform LF normalization +# Auto detect +## Handle line endings automatically for files detected as +## text and leave all files detected as binary untouched. +## This will handle all files NOT defined below. * text=auto + +# Source code +*.bash text eol=lf +*.bat text eol=crlf +*.cmd text eol=crlf +*.coffee text +*.css text diff=css +*.htm text diff=html +*.html text diff=html +*.inc text +*.ini text +*.js text eol=lf +*.mjs text eol=lf +*.cjs text eol=lf +*.json text eol=lf +*.jsx text eol=lf +*.less text +*.ls text +*.map text -diff +*.od text +*.onlydata text +*.php text diff=php +*.pl text +*.ps1 text eol=crlf +*.py text diff=python eol=lf +*.rb text diff=ruby eol=lf +*.sass text +*.scm text +*.scss text diff=css +*.sh text eol=lf +.husky/* text eol=lf +*.sql text eol=lf +*.styl text +*.tag text +*.ts text eol=lf +*.tsx text eol=lf +*.xml text +*.xhtml text diff=html + +# Docker +Dockerfile text + +# Documentation +*.ipynb text eol=lf +*.markdown text diff=markdown +*.md text diff=markdown eol=lf +*.mdwn text diff=markdown +*.mdown text diff=markdown +*.mkd text diff=markdown +*.mkdn text diff=markdown +*.mdtxt text +*.mdtext text +*.txt text +AUTHORS text +CHANGELOG text eol=lf +CHANGES text +CONTRIBUTING text eol=lf +COPYING text +copyright text +*COPYRIGHT* text +INSTALL text +license text eol=lf +LICENSE text eol=lf +NEWS text +readme text eol=lf +*README* text eol=lf +TODO text eol=lf + +# Templates +*.astro text eol=lf +*.dot text +*.ejs text +*.erb text +*.haml text +*.handlebars text eol=lf +*.hbs text eol=lf +*.hbt text +*.jade text +*.latte text +*.mustache text +*.njk text +*.phtml text +*.svelte text eol=lf +*.tmpl text +*.tpl text +*.twig text +*.vue text eol=lf + +# Configs +*.cnf text +*.conf text +*.config text eol=lf +.editorconfig text eol=lf +.env text eol=lf +.gitattributes text eol=lf +.gitconfig text +.htaccess text +*.lock text -diff +package.json text eol=lf +package-lock.json text eol=lf -diff +pnpm-lock.yaml text eol=lf -diff +.prettierrc text eol=lf +yarn.lock text -diff +*.toml text eol=lf +*.yaml text eol=lf +*.yml text eol=lf +browserslist text +Makefile text +makefile text +# Fixes syntax highlighting on GitHub to allow comments +tsconfig.json linguist-language=JSON-with-Comments + +# Heroku +Procfile text + +# Graphics +*.ai binary +*.bmp binary +*.eps binary +*.gif binary +*.gifv binary +*.ico binary +*.jng binary +*.jp2 binary +*.jpg binary +*.jpeg binary +*.jpx binary +*.jxr binary +*.pdf binary +*.png binary +*.psb binary +*.psd binary +# SVG treated as an asset (binary) by default. +*.svg text +# If you want to treat it as binary, +# use the following line instead. +# *.svg binary +*.svgz binary +*.tif binary +*.tiff binary +*.wbmp binary +*.webp binary + +# Audio +*.kar binary +*.m4a binary +*.mid binary +*.midi binary +*.mp3 binary +*.ogg binary +*.ra binary + +# Video +*.3gpp binary +*.3gp binary +*.as binary +*.asf binary +*.asx binary +*.avi binary +*.fla binary +*.flv binary +*.m4v binary +*.mng binary +*.mov binary +*.mp4 binary +*.mpeg binary +*.mpg binary +*.ogv binary +*.swc binary +*.swf binary +*.webm binary + +# Archives +*.7z binary +*.gz binary +*.jar binary +*.rar binary +*.tar binary +*.zip binary + +# Fonts +*.ttf binary +*.eot binary +*.otf binary +*.woff binary +*.woff2 binary + +# Executables +*.exe binary +*.pyc binary +# Prevents massive diffs caused by vendored, minified files +**/.yarn/releases/** binary +**/.yarn/plugins/** binary + +# RC files (like .babelrc or .eslintrc) +*.*rc text eol=lf + +# Ignore files (like .npmignore or .gitignore) +*.*ignore text eol=lf + +# Prevents massive diffs from built files +dist/* binary diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md new file mode 100644 index 00000000..40df0a30 --- /dev/null +++ b/.github/CHANGELOG.md @@ -0,0 +1,174 @@ +# Changelog + + + +## What's Happening + + + + + +### 1.2.6 - August 4, 2024 – The Resurrection Update + +Below you can see a copy of [the article from Bleverse Docs](https://docs.bleverse.com/en/blog/relivator/v126), which may be a bit outdated below. Please refer to [this blog post](https://docs.bleverse.com/en/blog/relivator/v126) to read the most recent version. Bleverse Docs also has translations of the article into other languages; and will contain even more information about Relivator than this README.md, including notes from all past and future releases. + +**Relivator is Back with Version 1.2.6!** 🥳 + +We are excited to announce the release of Relivator 1.2.6! This version marks the end of the "all-in-one" approach as we prepare for a more modular future with Reliverse CLI starting from version 1.3.0. The 1.2.6 release includes significant updates, especially in the database logic. The README.md has been significantly updated. Moving forward, we will introduce Canary, Release Candidate (RC), and General Availability (GA) branches for better version management. 1.2.6 will serve as a foundation, helping us transition more smoothly to the release of those 1.3.0's branches. + +### Major Changes and Improvements + +- **Database Updates**: This is the last release that simultaneously supports PostgreSQL/MySQL and NextAuth.js/Clerk integrations. +- **React 19 Preparation**: Work has commenced on upgrading from React 18 to React 19. +- **Updated Libraries**: The project now uses next-auth v5, clerk v5 and optionally supports tailwindcss v4. Refer to the updated README.md for more details. + +### Migration Guidance + +Starting from version 1.3.1, we will provide comprehensive guides for migrating from older versions. The usual migration process involves reviewing commit changes and integrating necessary updates into your custom code. However, due to the extensive changes in versions 1.2.6 and 1.3.0, this method is not feasible. We recommend reinstalling the project and transferring your custom features from the previous version to the new version of starter. Thank you for your understanding! + +To make the migration as smooth as possible, it's recommended to create a "`cluster`" folder in "`src`" and moving all your custom code there. If needed, you can adjust the paths using the [Find and Replace](https://code.visualstudio.com/docs/editor/codebasics#_search-and-replace) feature in VSCode. This will make it much easier to save and transfer your custom code to Relivator 1.2.6. + +### Default Database Change + +Neon PostgreSQL is now the default database instead of PlanetScale MySQL, as the latter no longer offers a free tier. If you require MySQL, [Railway](https://railway.app?referralCode=sATgpf) offers a more affordable alternative with a $5 credit without requiring a credit card. Note that this version has been primarily tested with Neon PostgreSQL. + +### Security and Code Improvements + +- **Type Safety and Editor Autocomplete**: This update enhances type safety and editor autocomplete for Drizzle ORM libraries. +- **Prettier Replaced by Biome**: Prettier has been removed in favor of Biome. The Pterrier's config will be removed in the next version from the `addons\terminal\reliverse\relimter\core\temp` folder. You can re-add it by running the `reliverse` command starting from Relivator 1.3.0. + +### Reliverse Scripts Transition + +Reliverse scripts have moved from the "unknown viability" stage to the "unstable" stage. As always, use them at your own risk and make backups. These scripts are now located in the `src/tools/unstable` folder. Most scripts require Python to be installed. + +For more details on this update, you can read my detailed posts in the Relivator thread on Discord. Start with [this message](https://discord.com/channels/1075533942096150598/1155482425862922370/1241995095125786624). + +### Release Notes 1.2.5-1.0.0 + +**This is what happened before 1.2.6 version:** + +
+ v1.2.5 — 27.02.2024 + +Hello! I, @blefnk Nazar Kornienko, finally have the opportunity to get back to working on Relivator after a month of exams at university. Thanks to all the new starter users! The project already has over 520 stars, 110 forks, 20 repository watchers, and 45 users in Discord - that's really cool and exciting! + +I also want to thank the active Discord users of the project: *codingisfun, el_ade, righthill, nyquanh, spacecoder315, adelinb*. Thank you to everyone who creates PR/Issues and everyone else who actively uses the starter, whose nicknames I don't know. Your feedback and contributions are incredibly valuable for the development of the project! + +Since there hasn't been an update in over a month, I'm going to make the transition to the next major version smoother. Therefore, version 1.2.5 has been released to simply refresh the dependencies and other minor details and README a bit. This small update will also allow me to check if certain users continue to have the individual strange problems they reported. + +If everything goes well, the next update will already be version 1.3.0. By the way, I'm working on 1.2.x and 1.3.0 in parallel, like in big studios, haha. But please note: some files and lines of code was disabled by default for this version to fix and check some things. By the way, the third digit means that this update is not mandatory, but still recommended. And Relivator 1.3.0 may or may not come with a canary version of React/Next.js to start preparing for the upcoming release of React 19. + +Well, that's all for today, all the best to everyone, and may your `pnpm latest` and `pnpm appts` always be successful! As usual, I try to write a short announcement, but it turns out a few paragraphs, that's how we live! 😄 + +P.S. And, please, don't pay attention that so many files have been "changed" in the latest commit, looks like it's because of Prettier I think, I only updated a few files, and if it's important to someone, please let me know in Discord's DM and I'll list you these files. + +[Read more about v1.2.5](https://github.com/blefnk/relivator-nextjs-template/releases/edit/1.2.5) + +
+ +
+ v1.2.4 — 13.01.2024 + +Just a small hotfix to improve the developer experience. + +[Read more about 1.2.4](https://github.com/blefnk/relivator-nextjs-template/releases/edit/1.2.4) + +
+ +
+ v1.2.3 — 12.01.2024 + +Just a small hotfix to improve the developer experience. + +[Read more about 1.2.3](https://github.com/blefnk/relivator-nextjs-template/releases/edit/1.2.3) + +
+ +
+ 1.2.2 | 03.01.2024 + +1.2.2 brings ESLint Stylistic Plugin into the life. This will make the work with the project even more enjoyable. + +Remember, Relivator is designed to be beginner-friendly, so quite a lot of ESLint options are turned off, just turn on what you need. + +These turn-offs will be gradually eliminated as we move towards the massive 2.0.0, which will significantly raise the project's standards, being professional, will be even more convenient for beginners. + +[Read more about v1.2.2](https://github.com/blefnk/relivator-nextjs-template/releases/edit/1.2.2) + +
+ +
+ 1.2.1 | 02.01.2024 + +This is quite a small update compared to all the past ones, but this one also deserves the attention. Now, updates will generally be smaller but will appear more frequently. Thanks to this, it will be possible to easily update forks and independent projects that use Relivator as their base. + +Update v1.2.1 adds Chinese localization, and among other things, work has begun on the so-called token system, which will allow future versions to work with Figma design systems in an automated way. It will also help to make the styles in the project cleaner by significantly reducing the number of Tailwind classes. For this, Relivator now installs the wonderful package @tokenami, developed by @jjenzz; Jenna, thank you so much for this library! + +p.s. 1.2.1 is the first commit to the Relivator repository that no longer contains an emoji at the beginning of its name. Thanks to this, contributors to Relivator/Reliverse will no longer have to spend time inventing a suitable emoji. + +[Read more about v1.2.1](https://github.com/blefnk/relivator-nextjs-template/releases/edit/1.2.1) + +
+ +
+ 1.2.0 | [27.12.2023] 🎄 Relivator v1.2.0 is here! 🥳 Click to see the announcement 🎁 + +*Relivator 1.2.0 is already here! I, [@blefnk Nazar Kornienko](https://github.com/blefnk), am thrilled to wrap up this year 2023, proudly presenting this release to the OSS community! So, the result of over two months of hard work is finally here!* + +In this version, significant attention was focused on stability, security, performance optimization, and a substantial improvements in design—both visually, UX, and the logic of how the app works. A lot was really done, too much to list everything! Be sure to install it and check it out! + +By the way, you can now enjoy a finely-tuned ESLint Flat Config! Also, it's worth noting that Clerk, since version 1.1.0, is no longer considered deprecated in the Relivator project. Thanks to 1.2.0, Clerk now works seamlessly with an easy switch to NextAuth.js when needed, all on the fly. Plus, full support for Turbopack (next dev --turbo) is finally here, even for next-intl! + +As for next-intl, finally, we can now enjoy internationalization that works not only on the client-side but also on the server! Only the 404 page has client-side i18n messages, all other pages and components use i18n as server-first. And this is really cool! + +Many unique solutions have been implemented in this new version. Moreover, using Relivator from this version, you have the opportunity to try out the alpha version of our unique Code-First/No-Code Builder system for React pages and components (which will appear in Reliverse CMS in the future). Just visit the Admin page while in the development environment and enjoy. + +If you have already used Relivator before, please pay attention, this is very important! Be sure to check the updated .env.example file and update the .env file accordingly. + +As a small teaser/spoiler, for Relivator 1.3.0, even more improvements in visual design and UX are planned; 1.4.0 will come with a magical CLI implementation, allowing you to quickly obtain only the necessary features and dependencies for the app (even automated updates and the ability to add other functions and packages to an already installed app); 1.5.0 will undergo a full code refactoring that will meet all the best standards and practices; 1.6.0-2.0.0+ versions, apart from many other things, will receive most of the items currently unchecked in the Roadmap (located in the project's README.md). It's going to be incredible! + +So, install this new version of Relivator 1.2.0 and appreciate the multitude of incredible features, and freely use it in the own projects today. Please use the preferred feedback channels to share the thoughts on Relivator 1.2.0 and what you would like to see in future releases. + +Don't forget to also check out the significantly updated README.md, it's worth it. + +Enjoy! ❄️☃️ Merry Christmas and Happy New Year 2024! 🎇🥳 + +
+ +
+ 1.1.0 | 🔥 The Most Feature-Rich Next.js 15 Starter + +Here it is! Relivator has been updated to version 1.1.0! + +**Now it's even more feature-rich, with cleaner code, and a more stable Next.js starter.** + +Ever dreamed of having both MySQL/PostgreSQL and Clerk/NextAuth.js in one project with the ability to switch on the fly? And even if you hadn't thought about it – now it's possible. Mix and match at will – even more possibilities for on-the-fly switching are coming soon in the next releases of Relivator. + +Among many other new and fixed things, Stripe is now fully functional and comes with extensive docs in the form of comments within the relevant files. + +`Please star this repository` to show the support! Thank you to everyone who has shown interest in this project! + +Please check out the updated list of project features in the project's README. Enjoy and please share the feedback! + +[Read more about v1.1.0](https://github.com/blefnk/relivator-nextjs-template/releases/edit/1.1.0) + +
+ +
+ 1.0.0 | 🎉 Relivator Release + +How to Install and Get Started? Please visit [the project's README](../README.md#readme), where you can always find up-to-date information about the project and how to install it easily. + +[Read more about v1.0.0](https://github.com/blefnk/relivator-nextjs-template/releases/edit/1.0.0) + +
+ +Please visit the CHANGELOG.md or [Bleverse Docs](https://docs.bleverse.com/en/blog/relivator/changelog) to read the older versions release notes. + +### Everyone! Thank You 🙏 + +If anyone have any questions or issues, don't hesitate to contact me, means @blefnk, on Discord or GitHub. For more information about 1.2.6 and 1.3.0, please visit `#⭐-relivator` chat on the project's Discord server and the [GitHub Issues](https://github.com/blefnk/relivator-nextjs-template/issues) page. + +Thank you for your continued support and happy coding with Relivator 1.2.6! diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..eafdf5ef --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,81 @@ +# Code of Conduct + + + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participating in our project and community a harassment-free experience for everyone, regardless of individual differences. + +We commit to creating a friendly and respectful place for learning, teaching, and contributing. All participants in our community are expected to show respect and courtesy to others. + +## Our Standards + +**Positive Behaviors to Encourage:** + +- Demonstrating empathy and kindness +- Being respectful of varying opinions and viewpoints +- Giving and receiving constructive feedback +- Owning up to our mistakes and learning from them +- Prioritizing community well-being and success + +**Unacceptable Behaviors Include:** + +- Use of sexualized language or imagery, and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing private information without consent +- Inappropriate private contact without consent +- Other conduct deemed unprofessional in a community setting + +## Enforcement Responsibilities + +Our community leaders are tasked with clarifying these standards and enforcing them, taking fair and appropriate action to address any instances of unacceptable behavior. + +Leaders have the authority to moderate comments, commits, code, and other contributions not in line with this Code of Conduct. + +## Scope + +This Code of Conduct applies within all project spaces, and also when an individual is representing the project or its community in public spaces. + +## Enforcement Process + +Inappropriate behavior can be reported to community leaders at . All complaints will be reviewed and investigated and will result in a response deemed necessary and appropriate to the circumstances. + +## Enforcement Guidelines + +Leaders will follow these guidelines in determining consequences: + +### 1. Correction + +**Impact**: Inappropriate language or behavior. + +**Consequence**: Private warning and clarity on why the behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Impact**: A single or series of violations. + +**Consequence**: A warning with consequences for continued behavior. No interaction with those involved for a set period, including community and external channels. Continued violation may lead to a ban. + +### 3. Temporary Ban + +**Impact**: Serious or sustained inappropriate behavior. + +**Consequence**: Temporary ban from interaction or public communication with the community. No interaction with those involved is allowed. Further violation may lead to a permanent ban. + +### 4. Permanent Ban + +**Impact**: Pattern of serious violations. + +**Consequence**: Permanent ban from public interaction within the community. + +## Attribution + +This Code of Conduct is adapted from the Contributor Covenant and Mozilla's code of conduct enforcement ladder. + +- [Contributor Covenant](https://contributor-covenant.org/version/2/0/code_of_conduct.html) +- [Mozilla's Enforcement Ladder](https://github.com/mozilla/diversity) + +For FAQs and translations, visit [Contributor Covenant FAQ](https://contributor-covenant.org/faq) and [Translations](https://contributor-covenant.org/translations). diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000..19b98ce2 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,73 @@ +# Contributing + + + +[![Join the Reliverse Discord](https://discordapp.com/api/guilds/1075533942096150598/widget.png?style=banner2)][bleverse-discord] + +Welcome, and thank you for considering contributing to this project! Your involvement is vital to the growth and success of this open-source initiative. + +## Getting Started + +- **Fork and Setup**: Initiate your contribution journey by forking the repository. Follow the setup instructions in [README.md](../README.md) for guidance. +- **Code of Conduct**: Be familiar with our [Code of Conduct](./CODE_OF_CONDUCT.md). Adherence to these principles is expected from all community members. + +## Contribution Guidelines + +### Legal Compliance + +- **Originality**: Ensure that your contributions are entirely original work. +- **Rights**: Verify that you hold the necessary rights to your contributions. +- **License Adherence**: All contributions must align with the project's licensing terms. + +### Pull Request Protocol + +- **UI Contributions**: For UI-related changes, utilize the [UI Pull Request Template](.github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml). Include screenshots to facilitate the review process. +- **General Contributions**: For other types of contributions, employ the [General Template](.github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml). Provide a description of your contribution. + +### Bug Reporting Process + +#### Preliminary Steps + +- **Research**: Confirm that the bug has not been reported already. +- **Comprehensive Information**: Compile all relevant details to accurately depict the issue. + +#### How to Submit a Bug Report + +- **GitHub Issues**: Utilize [GitHub Issues](https://github.com/blefnk/relivator-nextjs-template/issues/new) for reporting bugs. +- **Clarity and Detail**: Clearly articulate both the expected and actual behaviors, and describe the steps to replicate the bug. +- **Security Concerns**: For reporting security vulnerabilities, please contact us directly at instead of using public channels. + +### Enhancement Proposals + +#### Preliminary Considerations + +- **Version Check**: Ensure you're working with the latest version of the software. +- **Existing Suggestions**: Search [GitHub Issues](https://github.com/blefnk/relivator-nextjs-template/issues?q=) and our [Discord Suggestions Channel](https://discord.com/channels/1075533942096150598/1196425440777224212) to see if the suggestion has already been made. +- **Project Alignment**: Assess whether the suggestion is in line with the project's objectives. + +#### How to Suggest Enhancements + +- **Descriptive Title**: Choose a title that succinctly describes the enhancement. +- **Detailed Proposal**: Provide a step-by-step breakdown of the suggested enhancement. +- **Justification**: Explain why this enhancement would be valuable to the project. + +### Making the First Code Contribution + +1. **Issue Selection**: Choose an issue from [GitHub issues](https://github.com/blefnk/relivator-nextjs-template/issues?q=) and request its assignment. +2. **Fork and Branch Creation**: Fork the repository and create a new branch, naming it after the issue number. +3. **Committing Changes**: Adhere to a clear commit style, referencing the issue in the PR or commit message. +4. **Submitting a Pull Request**: Propose the changes through a pull request from the forked repository. + +## Additional Resources + +### Examples of Effective Commit Names + +| 🟢 Effective Example | 🟡 Less Effective Example | 🔴 Poor Example | +| --------------------------------------------------- | ------------------------- | ------------------ | +| add(i18n): add internationalization support for RSC | added new stuff | YOOOOOO | +| reposition cart for user convenience | fixing cart issue | fixing the thing | +| 📚 upd(docs): fix typos in README | doc updates | (docs)Trust me bro | + +[bleverse-discord]: https://discord.gg/Pb8uKbwpsJ diff --git a/.github/GITGUIDE.md b/.github/GITGUIDE.md new file mode 100644 index 00000000..06e840d1 --- /dev/null +++ b/.github/GITGUIDE.md @@ -0,0 +1,929 @@ +# The Detailed Git Guide + + + +*It is recommended to install Relivator according to the detailed instructions in [README.md](https://github.com/blefnk/relivator#readme) to feel more confident when starting to learn Git.* + +That's true. [Git](https://git-scm.com) can be complex at first. Consider using resources like the current guide, [Git Book](https://git-scm.com/book), and [GitHub Skills](https://skills.github.com) to deepen your understanding. The command *git commit --help* will redirect you to information about the git commit command and its options, so this help command can be beneficial as well. The best way to get comfortable with Git is to use it regularly. Create a small project or use a large web project template like [Relivator](https://github.com/blefnk/relivator-nextjs-template) and experiment with different commands. If you're ever unsure about something related to Git, use the current detailed guide article to learn more about Git. + +## Git Initial Setup + +*By following the details below in this guide, you will get a good start with Git, setting up your environment, and using some handy aliases to streamline your workflow. Happy gitting!* + +### Essential Tools + +Ensure you have [*Git*](https://learn.microsoft.com/en-us/devops/develop/git/install-and-set-up-git) installed. It is also recommended to install: *Node.js LTS* ([Windows/macOS](https://nodejs.org) | [Linux](https://youtu.be/NS3aTgKztis)). Then, run *corepack enable pnpm* to install [*pnpm*](https://pnpm.io/installation). Additionally, we recommend installing [*VSCode*](https://code.visualstudio.com) and *GitHub Desktop* ([Windows/macOS](https://desktop.github.com) | [Linux](https://dev.to/rahedmir/is-github-desktop-available-for-gnu-linux-4a69)). If you're a Windows user, install [PowerShell 7.4+](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.4#installing-the-msi-package) as well. + +### Setting Up Your Identity + +Before you start creating any commits in Git, you need to set your identity. This is important because your name and email will be added to every commit you make. This information is public, so use something appropriate. + +```bash +git config --global user.name "" +git config --global user.email "" +``` + +### Checking Your Settings + +To see all your Git settings and ensure they are correct, run: + +```bash +git config --global --list +``` + +## Git References + +Writing good commits is a good skill. To learn how to write good commit messages, refer to the following resources: + +- [Enhance Your Git Log with Conventional Commits](https://dev.to/maxpou/enhance-your-git-log-with-conventional-commits-3ea4) +- [Karma Commit Messages](http://karma-runner.github.io/6.4/dev/git-commit-msg.html) +- [Semantic Commit Messages](https://seesparkbox.com/foundry/semantic_commit_messages) +- [A Note About Git Commit Messages](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) +- [Writing Git Commit Messages](https://365git.tumblr.com/post/3308646748/writing-git-commit-messages) + +## Aliases + +Git aliases are shortcuts for longer commands. They can save you a lot of typing and make your workflow more efficient. + +### Receiving Updates + +This alias updates your local repository by pulling the latest changes, rebasing, and updating submodules. + +```bash +# git down +git config --global alias.down '!git pull --rebase --autostash; git submodule update --init --recursive' +``` + +### Sending Updates + +This alias pushes your changes to the remote repository, including tags. + +```bash +# git up +git config --global alias.up '!git push; git push --tags' +``` + +### Undo Staging of One or More Files + +Sometimes you stage files by mistake. This alias helps you unstage them. + +```bash +# git unstage +git config --global alias.unstage 'reset HEAD --' +``` + +### Tagging Releases According to Semantic Versioning (SemVer) + +Semantic Versioning is a way to tag your releases with meaningful version numbers. These aliases help automate the process. + +```bash +# git release-major +git config --global alias.release-major '!latest=$(git describe --abbrev=0 --tags 2>/dev/null); latest=${latest:-v0.0.0}; set -- $(echo $latest | sed -e s/v// -e "s/\./ /g"); major=$1; minor=$2; patch=$3; major=$((major+1)); minor=0; patch=0; next=v$major.$minor.$patch; git tag -a $next -m ""; echo "Previous release:"; echo -n " "; echo $latest; echo "New release:"; echo -n " "; echo $next' + +# git release-minor +git config --global alias.release-minor '!latest=$(git describe --abbrev=0 --tags 2>/dev/null); latest=${latest:-v0.0.0}; set -- $(echo $latest | sed -e s/v// -e "s/\./ /g"); major=$1; minor=$2; patch=$3; minor=$((minor+1)); patch=0; next=v$major.$minor.$patch; git tag -a $next -m ""; echo "Previous release:"; echo -n " "; echo $latest; echo "New release:"; echo -n " "; echo $next' + +# git release-patch +git config --global alias.release-patch '!latest=$(git describe --abbrev=0 --tags 2>/dev/null); latest=${latest:-v0.0.0}; set -- $(echo $latest | sed -e s/v// -e "s/\./ /g"); major=$1; minor=$2; patch=$3; patch=$((patch+1)); next=v$major.$minor.$patch; git tag -a $next -m ""; echo "Previous release:"; echo -n " "; echo $latest; echo "New release:"; echo -n " "; echo $next' +``` + +### Ignoring Redundant `git` Binary Names in Commands + +You can avoid typing `git git` by setting up an alias: + +```bash +# git git status, git git commit, etc. +git config --global alias.git '!cd "$GIT_PREFIX" && git' +``` + +### Displaying Changelog Since Latest Tag + +To see the changelog from the latest tag to your current commit, use this alias: + +```bash +# git changelog +git config --global alias.changelog '!git log $(git describe --abbrev=0 --tags)..HEAD --no-merges --pretty=oneline --abbrev-commit' +``` + +### Detecting Remnants and Leftovers from Development + +Find common leftover markers like TODOs or debug prints in your code: + +```bash +# git leftover +git config --global alias.leftover '!git grep -P -i -I --untracked "((? + ``` + +2. **Get updates from the original repository and push them to your fork:** + + ```bash + git pull upstream + git push origin + ``` + +### Reset a Repository to the Forked Repository's State + +1. **Add the original repository as a remote (do this only once):** + + ```bash + git remote add upstream + ``` + +2. **Reset your repository's state:** + + ```bash + git remote update + git reset --hard upstream/ + git push origin + + ``` + +### Show All Ignored Files for a Repository + +To list all ignored files: + +```bash +git clean -ndX +# or +git status --ignored +``` + +### Get a List of All Remotes for a Repository + +To see all remote repositories associated with your local repository: + +```bash +git remote -v +``` + +### Remove All Newly Ignored Files + +When you've added a file to `.gitignore` that was previously in the repository, remove it from the repository: + +```bash +git rm -r --cached . +git add . +``` + +### Changing the URL of a Repository's Remote + +To change the remote URL: + +```bash +git remote set-url +``` + +### Discard Unstaged Changes + +To discard all unstaged changes: + +```bash +git checkout -- . +``` + +To discard changes for a specific file or path: + +```bash +git checkout -- "" +``` + +### Undo a Commit That Has Already Been Published + +**Safe method:** + +```bash +git checkout HEAD~1 . +git commit -m "Undo some commit" +git push +``` + +**Dangerous method:** + +```bash +git reset --hard HEAD~1 +git push -f +``` + +### Undo a Local Commit (Not Published Yet) + +To keep the changes in your working copy: + +```bash +git reset --soft HEAD~1 +``` + +To discard the changes altogether: + +```bash +git reset --hard HEAD~1 +``` + +### Show Changes Made to the Working Copy + +To show unstaged changes only: + +```bash +git diff +``` + +To show staged changes only: + +```bash +git diff --staged +``` + +To show both unstaged and staged changes: + +```bash +git diff HEAD +``` + +### Delete a Branch + +To delete a branch locally: + +```bash +git branch -d +``` + +To delete a branch on the remote: + +```bash +git push : +``` + +### Adding a Description to a Commit + +To add a commit message with both a title and a description: + +```bash +git commit -m "" -m "<DESCRIPTION>" +``` + +### Remove All Untracked Files and Directories + +To preview what will be deleted: + +```bash +git clean -ndf +``` + +To actually delete the files: + +```bash +git clean -df +``` + +### Show the Log in a Short Version + +To display the commit log in a condensed format: + +```bash +git log --pretty=oneline --abbrev-commit +``` + +### Create a Branch + +To create a new branch but stay on the current branch: + +```bash +git branch <NEW_BRANCH_NAME> +``` + +To create and switch to a new branch: + +```bash +git checkout -b <NEW_BRANCH_NAME> +``` + +### Switch to Another Branch + +To switch to another branch: + +```bash +git checkout <OTHER_BRANCH_NAME> +``` + +### Tagging Releases + +You can mark specific points in your repository's history by adding tags. Tags are commonly used for releases but can be used for other purposes as well. + +To tag the current commit, use the following commands. Replace `<TAG_NAME>` with the unique name for the tag (e.g., `v1.0.4` for versioning) and `<DESCRIPTION>` with a description of the changes (optional). + +```bash +git tag -a "<TAG_NAME>" -m "<DESCRIPTION>" +git push <REMOTE_NAME> --tags +``` + +### Importing Commits, Pull Requests, and Other Changes via Patch Files + +1. Get the patch file for the commit, pull request, or change you want to import. For GitHub pull requests, you can get the patch file by appending `.patch` to the URL of the pull request: + + ```bash + curl -L https://github.com/<USER>/<REPO>/pull/<ID>.patch + ``` + +2. Apply the patch file using `git apply`: + + ```bash + curl -L https://github.com/<USER>/<REPO>/pull/<ID>.patch | git apply + ``` + +3. Optionally, make additional changes to the imported code. + +4. Commit the changes, mentioning the original author of the patch: + + ```bash + git commit --author "<ORIGINAL_AUTHOR_NAME> <<ORIGINAL_AUTHOR_EMAIL>>" -m "<YOUR_COMMIT_MESSAGE>" + ``` + +### Copying a Branch + +To create a local copy of an old branch under a new name and push it to the remote: + +```bash +git checkout -b <NEW_BRANCH_NAME> <OLD_BRANCH_NAME> +git push -u <REMOTE_NAME> <NEW_BRANCH_NAME> +``` + +### Moving a Branch + +To rename a branch locally and on the remote: + +```bash +git checkout -b <NEW_BRANCH_NAME> <OLD_BRANCH_NAME> +git push -u <REMOTE_NAME> <NEW_BRANCH_NAME> +git branch -d <OLD_BRANCH_NAME> +git push origin :<OLD_BRANCH_NAME> +``` + +### Clearing a Branch and Resetting it to an Empty State + +To create a new branch with no history and start fresh: + +```bash +git checkout --orphan <NEW_BRANCH_NAME> +rm -rf ./* +# Add your new files +git add . +git commit -m "Initial commit" +git push -uf <REMOTE_NAME> <NEW_BRANCH_NAME> +``` + +### Counting Commits on a Branch + +To count the total number of commits on a branch: + +```bash +git rev-list --count <BRANCH_NAME> +# Example: git rev-list --count main +``` + +To count commits per author: + +```bash +git shortlog -s -n +``` + +## Undoing Changes + +### Undo Git Reset + +If you mistakenly ran `git reset --hard HEAD^` and lost commits, use `git reflog` to find the commit and reset to it: + +```bash +git reflog +git reset 'HEAD@{1}' +``` + +### Undo Last Commit + +To undo the last commit but keep the changes in your working directory: + +```bash +git reset --soft HEAD~1 +``` + +### Finding Folder Size + +To find the size of a folder: + +```bash +du -hs +``` + +### Clearing Git History + +To remove files from history, use `git filter-branch`: + +```bash +git filter-branch --index-filter 'git rm --cached --ignore-unmatch <pathname>' <commitHASH> +``` + +Or use `bfg`: + +1. Install `bfg`: + + ```bash + brew install bfg + ``` + +2. Run `bfg` to clean commit history: + + ```bash + bfg --delete-files *.mp4 + bfg --replace-text passwords.txt + bfg --delete-folders .git + ``` + +3. Remove files: + + ```bash + git reflog expire --expire=now --all && git gc --prune=now --aggressive + ``` + +To replace text, create a `passwords.txt` file with the following format: + +```plaintext +PASSWORD1 # Replace literal string 'PASSWORD1' with '***REMOVED***' (default) +PASSWORD2==>examplePass # Replace with 'examplePass' instead +PASSWORD3==> # Replace with the empty string +regex:password=\w+==>password= # Replace using a regex +``` + +### Squash Commits + +To combine multiple commits into one: + +```bash +git rebase -i HEAD~<n> +# or +git rebase -i <COMMIT_HASH> +``` + +### Undo Your Changes + +To discard all changes: + +```bash +git reset +git checkout . +git clean -fdx +``` + +### Remove `node_modules` if Accidentally Checked In + +```bash +git rm -r --cached node_modules +``` + +### Amend Your Commit Messages + +To change the commit message of the most recent commit: + +```bash +git commit --amend +``` + +### Cherry-Picking + +To apply a commit from another branch as a new commit: + +```bash +git cherry-pick <YOUR_COMMIT_HASH> +``` + +## Branch Management + +### Rename a Branch + +To rename a branch, you can use the following commands: + +```bash +# Rename the branch from old-name to new-name +git branch -m old-name new-name +# Or, if you are on the branch you want to rename +git branch -m new-name + +# Delete the old branch on the remote and push the new branch +git push origin :old-name new-name + +# Set the upstream branch for the new branch +git push origin -u new-name +``` + +### Reset Local Repository Branch to Match Remote + +To reset your local branch to match the remote repository's `main` branch: + +```bash +git fetch origin +git reset --hard origin/main +git clean -f # Clean local files +``` + +### Delete All Merged Remote Branches + +To delete all remote branches that have already been merged: + +```bash +git branch -r --merged | grep -v main | sed 's/origin\///' | xargs -n 1 git push --delete origin +``` + +### Reset to Origin + +To reset your local branch to match the remote: + +```bash +git fetch --all + +# Option 1: Reset to main branch +git reset --hard origin/main + +# Option 2: Reset to a specific branch +git reset --hard origin/<branch_name> +``` + +### Get Latest Commit of Repository + +To get the latest commit of the repository: + +```bash +git log -1 +``` + +Press `Q` to exit the log view. + +### Get Hash from Latest Commit + +To get the full hash of the latest commit: + +```bash +git log -1 --pretty=%H +# Output +706b92ba174729c6a1d761a8566a74f0a0bf8672 +``` + +To get the abbreviated hash: + +```bash +git log -1 --pretty=%h +# Output +706b92b +``` + +To store the hash in a variable: + +```bash +echo $(git log -1 --pretty=%H) +``` + +### Tagging for Docker Versioning + +Tag the repository and perform a commit: + +```bash +# Tag the repository +git tag -a v0.0.1 -m "version v0.0.1" + +# Check the tag +git describe +# Output: v0.0.1 + +# Perform a commit +git commit -am 'chore: do something' + +# Describe again +git describe +# Output: v0.0.1-1-g9ba5c76 +``` + +### Git Shortcuts + +Set up aliases to simplify common Git commands: + +```bash +alias gst='git status' +alias gcm='git commit -S -am' +alias gco='git checkout' +alias gl='git pull origin' +alias gpom="git pull origin main" +alias gp='git push origin' +alias gd='git diff | mate' +alias gb='git branch' +alias gba='git branch -a' +alias del='git branch -d' +``` + +### Getting the GitHub Repository Name and Owner + +To get the repository URL and name: + +```bash +git config --get remote.origin.url +git ls-remote --get-url +git remote get-url origin +# Output: https://github.com/username/repository.git + +basename $(git remote get-url origin) .git +# Output: repository +``` + +### Delete Branch Locally + +To delete a branch locally: + +```bash +git push origin --delete <branch_name> +``` + +### Clear Local Deleted Branches and Fetch All Other Branches + +```bash +git remote update --prune +``` + +### Remove All Local Branches Except the Current One + +```bash +git branch | grep -v "main" | xargs git branch -D +``` + +### Sort Branches by Last Commit Date + +To list branches sorted by the last commit date: + +```bash +git fetch --prune +git branch --sort=-committerdate +``` + +## Commit Management + +### Git Commit Messages + +- **feat**: A new feature visible to end users. +- **fix**: A bug fix visible to end users. +- **chore**: Changes that don't impact end users (e.g., changes to CI pipeline). +- **docs**: Changes to documentation. +- **refactor**: Changes to production code focused on readability, style, or performance. + +### List Branches that Have Been Merged + +```bash +git branch --merged +``` + +### List Branches that Have Not Been Merged + +```bash +git branch --no-merged +``` + +### Cleanup and Optimize Repository + +To clean up unnecessary files and optimize the local repository: + +```bash +# Cleanup unnecessary files +git gc + +# Prune all unreachable objects from the object database +git prune + +# Verify the connectivity and validity of objects in the database +git fsck + +# Prune your remote working directory +git remote update --prune +``` + +### Push Commits with Tags Automatically + +```bash +git config --global push.followTags true +``` + +### Restore a File to a Given Commit + +To restore a specific file to its state at a given commit: + +```bash +git restore -s <SHA1> -- <filename> +``` + +## Useful Git Commands and Techniques + +### Download Just a Folder from GitHub with Subversion (SVN) + +To download a specific folder from a GitHub repository using SVN: + +```bash +# Replace tree/main with trunk in the URL +svn export https://github.com/alextanhongpin/pkg.git/trunk/authheader +``` + +To create an alias for downloading `docker-compose` templates: + +```bash +alias init-db='svn export https://github.com/alextanhongpin/docker-samples/trunk/postgres/docker-compose.yml' +``` + +### Pre-Commit Hooks + +Ensure you have a changelog edited in your current branch. Use a pre-commit hook to enforce this: + +```bash +#!/bin/bash + +if [[ $(git diff develop -- CHANGELOG.md | wc -l) -eq 0 ]]; then + echo "Don't forget to add CHANGELOG.md" + exit 1 +fi +``` + +### Git Rebase Favor Current Branch + +To favor the current branch during a rebase: + +```bash +git rebase -X theirs ${branch} +``` + +[More info on merge strategies for rebase](https://stackoverflow.com/questions/2945344/how-do-i-select-a-merge-strategy-for-a-git-rebase) + +### Git Post-Checkout Hook + +To automate tasks after checking out a branch, use a post-checkout hook: + +1. Create and set permissions for the hook: + + ```bash + touch .git/hooks/post-checkout + chmod u+x .git/hooks/post-checkout + ``` + +2. Add the following script to `.git/hooks/post-checkout`: + + ```bash + #!/bin/bash + + # Parameters + # $1: Ref of previous head + # $2: Ref of new head + # $3: Whether this is a file checkout (0) or branch checkout (1). + + # This is a file checkout - do nothing + if [ "$3" == "0" ]; then exit; fi + + BRANCH_NAME=$(git symbolic-ref --short -- HEAD) + NUM_CHECKOUTS=$(git reflog --date=local | grep -o ${BRANCH_NAME} | wc -l) + + # If the refs of the previous and new heads are the same + # and the number of checkouts equals one, a new branch has been created + if [ "$1" == "$2" ] && [ ${NUM_CHECKOUTS} -eq 1 ]; then + echo "new branch created" + else + echo "switched branch to ${BRANCH_NAME}" + fi + ``` + +### Git Checkout a Single File from Main Commit + +To revert a file to its state in the main branch: + +```bash +git checkout $(git rev-parse main) -- path-to-file +``` + +### Adding New Changes to the Latest Commit + +To amend the latest commit with new changes: + +```bash +git add --all +git commit --amend +# Note: You may need to force push if the commit has already been pushed +git push --force +``` + +### Cleaning a Branch PR + +If your branch is messy and you want to clean up the commits: + +1. Create a new temporary branch: + + ```bash + git checkout -b feature/foo-tmp + ``` + +2. Create a patch file of changes: + + ```bash + git diff origin/feature/foo origin/main > out.patch + ``` + +3. Apply the patch to the temporary branch: + + ```bash + git apply out.patch + ``` + +4. Clean up and rebase as needed, then delete the old branch: + + ```bash + git branch -D feature/foo + ``` + +5. Rename the temporary branch to the original name: + + ```bash + git branch -m feature/foo + ``` + +6. Force push the cleaned branch: + + ```bash + git push --force feature/foo + ``` + +### Better Push Force + +Use `git push --force-with-lease` instead of `git push --force` for safer forced updates. + +[Learn more about force-with-lease](https://stackoverflow.com/questions/52823692/git-push-force-with-lease-vs-force) + +### Git Fixup + +To fix up a previous commit: + +1. Create a fixup commit: + + ```bash + git commit --fixup <first-commit-hash> + ``` + +2. Rebase to squash the fixup commit: + + ```bash + git rebase -i --autosquash --root + ``` + +## Resources + +- [Relivator Next.js Template](https://github.com/blefnk/relivator#readme) +- [Theo's Post](https://youtube.com/post/UgkxE4zRFagfPNviZN2OgYMhozOa7MJSbktM) +- [Martin Heinz's Blog](https://martinheinz.dev/blog/109) +- [Delight-IM's Git Knowledge](https://github.com/delight-im/Knowledge/blob/master/Git.md) +- [Alex Tan Hong Pin's Cheat Sheet](https://github.com/alextanhongpin/cheat-sheet/blob/master/git.md) + +## The Bottom Line + +*This guide covers various useful Git commands and techniques, inspired by various resources and composed by Reliverse, making it easier for both beginners and advanced users to manage and optimize their repositories.* diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT.yml b/.github/ISSUE_TEMPLATE/BUG_REPORT.yml new file mode 100644 index 00000000..d8acb1e3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/BUG_REPORT.yml @@ -0,0 +1,38 @@ +name: Bug Report +description: Create a bug report to help us improve +title: "bug: " +labels: + - bug +body: + - type: textarea + attributes: + label: Provide environment information + description: | + Run this command in the project root and paste the results in a code block: + ```bash + pnpm system + ``` + validations: + required: true + - type: textarea + attributes: + label: Describe the bug + description: A clear and concise description of the bug, as well as what you expected to happen when encountering it. + validations: + required: true + - type: input + attributes: + label: Link to reproduction + description: Please provide a link to a reproduction of the bug. Issues without a reproduction repo may be ignored. + validations: + required: true + - type: textarea + attributes: + label: To reproduce + description: Describe how to reproduce the bug. Steps, code snippets, reproduction repos etc. + validations: + required: true + - type: textarea + attributes: + label: Additional information + description: Add any other information related to the bug here, screenshots if applicable. diff --git a/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml new file mode 100644 index 00000000..6818b9d3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml @@ -0,0 +1,26 @@ +name: Feature Request +description: Create a feature request for the core packages +title: "feat: " +labels: + - enhancement +body: + - type: markdown + attributes: + value: | + Thank you for taking the time to file a feature request. Please fill out this form as completely as possible. + - type: textarea + attributes: + label: Describe the feature you'd like to request + description: Please describe the feature as clear and concise as possible. Remember to add context as to why you believe this feature is needed. + validations: + required: true + - type: textarea + attributes: + label: Describe the solution you'd like to see + description: Please describe the solution you would like to see. Adding example usage is a good way to provide context. + validations: + required: true + - type: textarea + attributes: + label: Additional information + description: Add any other information related to the feature here. If the feature request is related to any issues or discussions, link them here. diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 00000000..4be5bd97 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,30 @@ +# Security Policy + +<!-- For those who are viewing the current markdown file using: +- VSCode: Press F1 or Cmd/Ctrl+Shift+P and enter ">Markdown: Open Preview". It is recommended to install the "markdownlint" and "Markdown All in One" extensions. +- GitHub: Does this .md file appear different from what you are used to seeing on GitHub? Ensure the URL does not end with "?plain=1". --> + +## Supported Versions + +Our commitment to security extends to the following version of the project: + +| Version | Supported | +| ------- | ------------------ | +| @latest | :white_check_mark: | + +## Reporting a Vulnerability + +We greatly value the security community's efforts in helping keep our project safe. If you've discovered a security vulnerability, the responsible disclosure is crucial for us. Here's how you can report it: + +1. **Contact Method**: Email us at <blefnk@gmail.com>. +2. **Email Subject**: Please use a concise yet descriptive subject, such as "Security Vulnerability Found". +3. **Vulnerability Details**: Provide a comprehensive description of the vulnerability. Include reproduction steps, and any other information that might help us understand and resolve the issue effectively. +4. **Proof of Concept**: Attach any proof-of-concept or sample code if available. Please ensure that the research does not involve destructive testing or violate any laws. +5. **Encryption**: For secure communication, use our public PGP key available on our website or public key servers. +6. **Response Timeline**: We aim to acknowledge the report within [e.g., 48 hours] and will keep you updated on our progress. +7. **Investigation and Remediation**: Our team will promptly investigate and work on resolving the issue. We'll maintain communication with you throughout this process. +8. **Disclosure Policy**: Please refrain from public disclosure until we have mitigated the vulnerability. We will collaborate with you to decide on an appropriate disclosure timeline, considering the issue's severity. + +We're grateful for the contributions to our project's security. Contributors who help improve our security may be publicly acknowledged (with consent). + +*Note: Our security policy may be updated periodically.* diff --git a/.github/TEMPLATE.md b/.github/TEMPLATE.md new file mode 100644 index 00000000..40ad5e82 --- /dev/null +++ b/.github/TEMPLATE.md @@ -0,0 +1,97 @@ +Here's the corrected version of your text: + +# Add Your Commit Title Here + +<!-- For those who are viewing the current markdown file using: +- VSCode: Press F1 or Cmd/Ctrl+Shift+P and enter ">Markdown: Open Preview". It is recommended to install the "markdownlint" and "Markdown All in One" extensions. +- GitHub: Does this .md file appear different from what you are used to seeing on GitHub? Ensure the URL does not end with "?plain=1". --> + +*This is the current repository's starting point for `git commit` messages. Feel free to edit this template to suit your needs. For more information about `git commit` messages, visit: [Git Commit Message Guidelines](https://github.com/joelparkerhenderson/git_commit_message)* + +## Relevant Links + +Reference links to relevant web pages, issue trackers, blog articles, etc. Examples: + +- See: <https://example.com> +- See: [Example Page](https://example.com) + +## Co-Authors + +List all co-authors to ensure proper credit. Examples: + +- Co-authored-by: Name <name@example.com> +- Co-authored-by: Name <name@example.com> + +## Why + +Describe the reason for this change. What are the goals, use cases, or stories behind it? + +## How + +Explain the implementation details. What algorithms, methods, or steps were used? + +## Tags + +Suitable tags for searching, such as hashtags or keywords. Examples: + +- Tags: + +## Help + +### Subject Line Guidelines + +Use imperative, uppercase verbs for the subject line: + +- **Add**: Create a capability (e.g., feature, test, dependency). +- **Drop**: Delete a capability (e.g., feature, test, dependency). +- **Fix**: Fix an issue (e.g., bug, typo, accident, misstatement). +- **Bump**: Increase the version of something (e.g., a dependency). +- **Make**: Change the build process, tools, or infrastructure. +- **Start**: Begin doing something (e.g., enable a toggle, feature flag, etc.). +- **Stop**: End doing something (e.g., disable a toggle, feature flag, etc.). +- **Optimize**: A change that improves performance (e.g., speed up code). +- **Document**: A change that affects only documentation (e.g., help files). +- **Refactor**: A change that is purely refactoring. +- **Reformat**: A change that is purely formatting (e.g., indent lines, trim spaces). +- **Rephrase**: A change that is purely textual (e.g., edit a comment, doc, etc.). + +### Subject Line Rules + +- Use 50 characters maximum. +- Do not end with a period. + +### Body Text Rules + +- Use as many lines as necessary. +- Wrap lines at 72 characters maximum. + +## Usage + +To use this template, place the file here: + +```plaintext +~/.git_commit_template.txt +``` + +Configure git to use the template: + +```bash +git config --global commit.template ~/.git_commit_template.txt +``` + +Add the following to your `~/.gitconfig` file: + +```plaintext +[commit] + template = ~/.git_commit_template.txt +``` + +Adjust the file location and usage as needed. + +## Tracking + +- Package: GITCOMMIT.md +- Version: 1.0.0 +- Updated: 2024-07-20T22:15:57Z +- License: MIT +- Contact: Nazar Kornienko (<blefnk@gmail.com>) diff --git a/.github/TRANSLATIONS.md b/.github/TRANSLATIONS.md new file mode 100644 index 00000000..366355bd --- /dev/null +++ b/.github/TRANSLATIONS.md @@ -0,0 +1,50 @@ +# Contributing Guidelines for Our Documentation + +<!-- For those who are viewing the current markdown file using: +- VSCode: Press F1 or Cmd/Ctrl+Shift+P and enter ">Markdown: Open Preview". It is recommended to install the "markdownlint" and "Markdown All in One" extensions. +- GitHub: Does this .md file appear different from what you are used to seeing on GitHub? Ensure the URL does not end with "?plain=1". --> + +[📖 Docs](https://docs.bleverse.com) + +Are you fluent in a language other than English? the expertise is invaluable to us! + +We utilize [README.md](../README.md) for the current repo and the [Starlight](https://starlight.astro.build) for our shared documentation website. This fantastic tool enables multilingual support within the same repository. This means you can contribute to the documentation in the preferred language without the hassle of managing a separate repository. For detailed guidance, please explore the [Starlight i18n documentation](https://starlight.astro.build/guides/i18n/). + +## How to Contribute + +It's important to start by reading our [contribution guidelines](./CONTRIBUTING.md), which provide essential information about contributing to this project. + +### Starting a New Translation + +If our documentation isn't yet available in the language and you're interested in translating it, here's how you can start: + +1. **Identify or Create a Translation Issue:** + + - Search for an existing issue for the language you wish to translate. If there isn't one, feel free to create it. This helps prevent duplicate efforts and enables the community to monitor translation progress. + - If you find an existing issue, please comment to inform others that you are starting the translation. We advise submitting individual pull requests (PRs) for each document to simplify the review process. + +2. **Configure Internationalization (i18n):** + + - Ensure that the i18n configuration is set up in Starlight. If not, set it up and submit a PR for this initial step. For instructions, visit ["Configure i18n"](https://starlight.astro.build/guides/i18n/#configure-i18n). + +3. **Initiate Translation Process:** + + - Copy the files/folders you intend to translate from the `src/content/docs` directory to a new or existing directory named after the language (for instance, `src/content/docs/de` for German). Exclude any already translated code. Starlight will automatically default to English for untranslated documents. Unsure about the language code? Find it [here](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes). + +4. **Translate and Create PRs:** + - Translate the copied files and submit a PR or Draft PR for each. Once completed, mark the PR as ready for review. + +### Helpful Tips + +- Sidebar translations are configured in `astro.config.ts`. +- Once you've completed the translations, please mark the pull request as ready for review. + +### Review Process + +Our goal is to have each PR reviewed by 1-2 people. This ensures high-quality documentation with a consistent tone. + +If you know someone proficient in the translation language, encouraging them to review the PR can expedite the process. Their expertise will not only enhance the quality of the translation but also speed up the integration of the valuable contribution. + +## Acknowledgements + +This document draws inspiration from the translation guidelines of [t3-oss/create-t3-app](https://github.com/t3-oss/create-t3-app/blob/main/www/TRANSLATIONS.md) and [biomejs/biome](https://github.com/biomejs/biome/blob/main/website/TRANSLATIONS.md). We thank these communities for their pioneering efforts in collaborative translation. diff --git a/.github/funding.yml b/.github/funding.yml index 00b72d74..b3f553ae 100644 --- a/.github/funding.yml +++ b/.github/funding.yml @@ -1,7 +1,6 @@ github: blefnk +patreon: blefnk +buy_me_a_coffee: blefnk custom: - [ - "https://patreon.com/blefnk", - "https://paypal.me/blefony", - "https://ko-fi.com/blefnk", - ] + - https://paypal.me/blefony + - https://ko-fi.com/blefnk diff --git a/.github/issue_template/1.bug_report.yml b/.github/issue_template/1.bug_report.yml deleted file mode 100644 index 15dbc37c..00000000 --- a/.github/issue_template/1.bug_report.yml +++ /dev/null @@ -1,39 +0,0 @@ -# @see https://github.com/vercel/next.js/blob/canary/.github/ISSUE_TEMPLATE/1.bug_report.yml - -name: 🐞 Bug Report -description: Create a bug report to help us improve -title: "bug: " -labels: ["🐞 unconfirmed bug"] -body: - - type: textarea - attributes: - label: Provide environment information - description: | - Run this command in your project root and paste the results in a code block: - ```bash - pnpm dlx envinfo --system --binaries - ``` - validations: - required: true - - type: textarea - attributes: - label: Describe the bug - description: A clear and concise description of the bug, as well as what you expected to happen when encountering it. - validations: - required: true - - type: input - attributes: - label: Link to reproduction - description: Please provide a link to a reproduction of the bug. Issues without a reproduction repo may be ignored. - validations: - required: true - - type: textarea - attributes: - label: To reproduce - description: Describe how to reproduce your bug. Steps, code snippets, reproduction repos etc. - validations: - required: true - - type: textarea - attributes: - label: Additional information - description: Add any other information related to the bug here, screenshots if applicable. diff --git a/.github/issue_template/2.feature_request.yml b/.github/issue_template/2.feature_request.yml deleted file mode 100644 index db55e08a..00000000 --- a/.github/issue_template/2.feature_request.yml +++ /dev/null @@ -1,27 +0,0 @@ -# @see https://github.com/vercel/next.js/blob/canary/.github/ISSUE_TEMPLATE/4.docs_request.yml - -name: 🛠 Feature Request -description: Create a feature request for the core packages -title: "feat: " -labels: ["✨ enhancement"] -body: - - type: markdown - attributes: - value: | - Thank you for taking the time to file a feature request. Please fill out this form as completely as possible. - - type: textarea - attributes: - label: Describe the feature you'd like to request - description: Please describe the feature as clear and concise as possible. Remember to add context as to why you believe this feature is needed. - validations: - required: true - - type: textarea - attributes: - label: Describe the solution you'd like to see - description: Please describe the solution you would like to see. Adding example usage is a good way to provide context. - validations: - required: true - - type: textarea - attributes: - label: Additional information - description: Add any other information related to the feature here. If your feature request is related to any issues or discussions, link them here. diff --git a/.github/translations/readme/polish.md b/.github/translations/readme/polish.md deleted file mode 100644 index eb3ab23f..00000000 --- a/.github/translations/readme/polish.md +++ /dev/null @@ -1,402 +0,0 @@ -# Relivator: Najbogatszy w funkcje starter Next.js 14 - -<!-- https://github.com/blefnk/relivator#readme --> - -🌐 [Relivator Demo](https://relivator.bleverse.com) - -<!-- **TŁUMACZENIE NIE JEST AKTUALNE Z v1.2.5 [WERSJI ANGLOJĘZYCZNEJ README.MD](https://github.com/blefnk/relivator#readme)!** --> - -**Uwaga: Poniższy tekst jest w większości maszynowym tłumaczeniem pliku [README.md](https://github.com/blefnk/relivator#readme). Aktywnie pracujemy nad jego ulepszeniem. Prosimy o zapoznanie się z oryginałem, jeśli niektóre fragmenty tekstu są niejasne.** - -Naszym celem jest stworzenie najbardziej bogatego w funkcje i globalnego startera Next.js na świecie. Oferuje więcej niż tylko kod - to podróż. Jest stabilny i gotowy do produkcji. Przewiń w dół i sprawdź zapierającą dech w piersiach listę funkcji projektu, w tym przełączanie między Clerk/NextAuth.js oraz MySQL/PostgreSQL "on-the-fly". - -## Jak zainstalować i rozpocząć - -1. **Niezbędne narzędzia**: Upewnij się, że [_VSCode_](https://code.visualstudio.com), [_Git_](https://learn.microsoft.com/en-us/devops/develop/git/install-and-set-up-git), _GitHub Desktop_ ([Windows/macOS](https://desktop.github.com/) | [Linux](https://dev.to/rahedmir/is-github-desktop-available-for-gnu-linux-4a69)) i _Node.js LTS_ ([Windows/macOS](https://nodejs.org) | [Linux](https://youtu.be/NS3aTgKztis)) są zainstalowane. -2. **Klonowanie projektu**: [_Utwórz nowy fork_](https://github.com/blefnk/relivator/fork) i użyj GitHub Desktop, aby go pobrać. -3. **Konfiguracja**: Otwórz VSCode i załaduj folder projektu. Naciśnij `Ctrl+Shift+P` i wyszukaj `>Create New Terminal`. Zainstaluj _PNPM_ używając `corepack enable pnpm`. Następnie wpisz `pnpm install`, aby zainstalować pakiety. Następnie skopiuj plik `.env.example` do nowego pliku `.env` i wypełnij przynajmniej pola `NEXT_PUBLIC_DB_PROVIDER` i `DATABASE_URL`. Na koniec wyślij schemat bazy danych do swojej bazy danych za pomocą `pnpm mysql:push` lub `pnpm pg:push`. -4. **Run, Stop, Build**: Użyj `pnpm dev` by uruchomić aplikację (odwiedź <http://localhost:3000> by to sprawdzić). Zatrzymaj ją, skupiając się na konsoli i naciskając `Ctrl+C`. Po wprowadzeniu zmian, zbuduj aplikację używając `pnpm build`. W porządku, jeśli zobaczysz ostrzeżenia Clerk. -5. **Commit and Deploy**: Prześlij projekt do swojego profilu GitHub za pomocą GitHub Desktop. Następnie wdróż go, importując projekt do [Vercel](https://vercel.com/new), dzięki czemu Twoja witryna będzie publicznie dostępna w Internecie. Jeśli chcesz podzielić się swoją pracą, uzyskać opinię lub poprosić o pomoc, możesz to zrobić [na naszym serwerze Discord](https://discord.gg/Pb8uKbwpsJ) lub [za pośrednictwem dyskusji GitHub](https://github.com/blefnk/relivator/discussions). - -Przewiń stronę w dół, aby zobaczyć wiele przydatnych informacji o tym, jak wszystko działa w projekcie. - -## Lista kontrolna funkcji projektu - -Przestań biegać od jednego startera do drugiego. Dzięki Relivator będziesz mieć nieograniczone możliwości. Możesz stworzyć wszystko, co chcesz; wszystkie narzędzia są już przygotowane, specjalnie dla Ciebie. - -**Uwaga:** Co dwa tygodnie darujemy wczesny dostęp do przyszłych wtyczek Relivator trzem losowo wybranym osobom. Po prostu dodaj `gwiazdkę do tego repozytorium` i [daj nam znać, jak się z Tobą skontaktować](https://forms.gle/NXZ6QHpwrxh52VA36). Aby dyskutować, dołącz do [Discord projektu](https://discord.gg/Pb8uKbwpsJ). - -- [x] Wykorzystano [Next.js 14](https://nextjs.org), [React 18](https://react.dev), [TailwindCSS](https://tailwindcss.com) i [TypeScript](https://typescriptlang.org) to podstawowe technologie projektu. -- [x] Wdrożono uwierzytelnianie poprzez **zarówno [Clerk](https://clerk.com/), jak i [NextAuth.js](https://authjs.dev)**. -- [x] Rozpętano szeroką internacjonalizację **w 10 językach** (_angielski, niemiecki, hiszpański, perski, francuski, hindi, włoski, polski, turecki, ukraiński_), używając [next-intl](<https://next-intl> -docs.vercel.app). -- [x] Podjęto próbę [Drizzle ORM](https://orm.drizzle.team), wykorzystując **bazy danych MySQL i PostgreSQL** oraz [PlanetScale](https://planetscale.com)/[Neon](https://neon.tech)/[Vercel](https://vercel.com)/[Railway](https://railway.app). -- [x] Pomyślnie skonfigurowano `next.config.mjs` z obsługą i18n i MDX. -- [x] Przez cały projekt dążyłem do dokładnej dokumentacji i przyjaznego podejścia dla początkujących. -- [x] Umiejętnie skonfigurowany i skomentowany `middleware.ts` dla i18n i next-auth. -- [x] Skonfigurowano system Content-Security-Policy (CSP) jako środek bezpieczeństwa zapobiegający atakom XSS (domyślnie wyłączony). -- [x] Podano przykładowe ustawienia VSCode i zalecane rozszerzenia. -- [x] Zoptymalizowano [Next.js Metadata API](https://nextjs.org/docs/app/building-your-application/optimizing/metadata) pod kątem SEO, integrując procedury obsługi systemu plików. -- [x] Zintegrowano wskaźnik rozmiaru ekranu TailwindCSS dla lokalnych uruchomień projektów. -- [x] Utworzono system subskrypcji i kasowania użytkowników przy użyciu [Stripe](hhttps://github.com/stripe/stripe-node#readme). -- [x] Zapewniono weryfikację bezpieczeństwa typów schematów projektu i pól interfejsu użytkownika przy użyciu [Zod](https://zod.dev). -- [x] Zatrudniłem [EsLint](https://eslint.org) i [Prettier](https://prettier.io), aby zapewnić bezpieczeństwo i czytelność kodu. -- [x] Elegancko wykonany system czcionek, wykorzystujący [Inter](https://rsms.me/inter) i dodatkowe kroje pisma. -- [x] Opracowano witrynę sklepową zawierającą funkcjonalność produktu, kategorii i podkategorii. -- [x] Zaprojektowano nowoczesny, przejrzyście skomponowany interfejs użytkownika przy użyciu [Radix](https://radix-ui.com) z atrakcyjnymi komponentami interfejsu użytkownika od [shadcn/ui](https://ui.shadcn.com). -- [x] Utworzono obszerny, przyjazny dla początkujących plik „README.md”, zawierający opisy [zmiennych środowiskowych] (<https://nextjs.org/docs/basic-features/environment-variables>). -- [x] Funkcjonalność bloga realizowana poprzez wykorzystanie plików MDX. -- [x] Zaimplementowano [tRPC](https://trpc.io) i [Zapytanie TanStack](https://tanstack.com/query) (z [React Normy](<https://github.com/klis87/> normy#readme)), aby mieć zaawansowane pobieranie danych serwera i klienta. -- [ ] Użyj ścieżek bezwzględnych, jeśli są stosowane. -- [ ] Użyj [Kysely](https://kysely.dev) z Drizzle, aby uzyskać pełne bezpieczeństwo typu kreatora zapytań SQL TypeScript. -- [ ] Przetłumacz README.md i powiązane pliki na więcej języków. -- [ ] Przekształć się poza prosty sklep e-commerce w uniwersalny starter stron internetowych. -- [ ] Uporządkuj `package.json` z poprawnie zainstalowanymi i uporządkowanymi pakietami w `zależnościach` i `devDependency`. -- [ ] Autor projektu powinien opublikować serię szczegółowych filmów wideo na temat korzystania z tego projektu. Powinni też znaleźć się pasjonaci chcący publikować w swoich zasobach własne filmy o projekcie. -- [ ] Zmniejsz w miarę możliwości liczbę pakietów projektu, plików konfiguracyjnych itp. -- [ ] Ogranicz zagnieżdżanie znaczników HTML i zapewnij ich prawidłowe użycie, jeśli to możliwe. -- [ ] Nadaj priorytet dostępności, zarówno w przypadku interfejsu użytkownika aplikacji (interfejsu użytkownika), UX (doświadczenia użytkownika), jak i DX (doświadczenia programisty) dla programistów. Zachowaj użyteczność bez uszczerbku dla estetyki. -- [ ] Preferuj `funkcję`/`typ` zamiast `const`/`interfejs`, aby zachować czytelny, czysty i przyjazny dla początkujących kod. -- [ ] Zoptymalizuj wszystkie elementy aplikacji, aby poprawić prędkość zimnego startu programistów i kompilacji. -- [ ] Przeprowadź migrację do NextAuth.js' [next-auth@beta](https://npmjs.com/package/next-auth?activeTab=versions) ([dyskusje](<https://github.com/nextauthjs/> next-auth/releases/tag/next-auth%405.0.0-beta.4)) i do [@clerk/*@alpha] Clerka. -- [ ] Przenieś każdy powiązany system do jego specjalnego folderu (do folderu `src/core`), aby każdy system mógł zostać łatwo usunięty z projektu, jeśli zajdzie taka potrzeba. -- [ ] Przenieś style komponentów do plików .css lub .scss lub użyj pakietów zapewniających lepszą składnię stylów w plikach .tsx. -- [ ] Zarządzaj weryfikacją e-maili, zapisami do biuletynu i marketingiem e-mailowym za pomocą opcji [Wyślij ponownie] (<https://resend.com>) i [Reaguj e-mailem] (<https://react.email>). -- [ ] Spraw, aby każda zmienna środowiskowa była opcjonalna, umożliwiając aplikacji działanie bez skonfigurowania czegokolwiek, po prostu pomijając określone sekcje kodu, jeśli to konieczne. -- [ ] Po wbudowaniu terminala programistycznego upewnij się, że każda strona i oprogramowanie pośrednie są zielone lub żółte, ale nie czerwone. -- [ ] Utrzymuj projekt w najlepszym możliwym sposobie pisania dobrego i czystego kodu, postępując zgodnie z wytycznymi takimi jak [Przewodnik po stylu JavaScript Airbnb](https://github.com/airbnb/javascript/tree/master/react) / [Airbnb Przewodnik po stylu React/JSX](https://github.com/airbnb/javascript/tree/master/react). -- [ ] Utrzymuj projekt wolny od `@ts-expect-error` i powiązanych rzeczy, które nie są zbyt bezpieczne dla typów. -- [ ] Utrzymuj jak najmniejszą liczbę plików cookie, przygotuj się na przyszłość bez plików cookie, wdrażaj zarządzanie plikami cookie i powiadomienia. -- [ ] Wprowadź system komentarzy do produktów, obejmujący typy recenzji i pytań. -- [ ] Zintegruj cenne spostrzeżenia z [Next.js Weekly](https://nextjsweekly.com/issues) z tym starterem. -- [ ] Zintegruj cenne rzeczy z [Przykładów Next.js](https://github.com/vercel/next.js/tree/canary/examples) do tego projektu. -- [ ] Wdrożyć inteligentny i ujednolicony system logów, zarówno na potrzeby programowania, jak i produkcji, zarówno dla konsoli, jak i zapisu do określonych plików. -- [ ] Zaimplementuj najlepsze rzeczy z [Payload CMS](https://github.com/payloadcms/payload) dzięki ulepszeniom Relivator. -- [ ] Implementuj przesyłanie plików za pomocą [UploadThing](https://uploadthing.com) i [Cloudinary](https://cloudinary.com). -- [ ] Zaimplementuj dynamiczne przełączanie między funkcjami aplikacji, takimi jak dostawca bazy danych, dokonując odpowiednich kontroli zmiennych środowiskowych. -- [ ] Zaimplementuj pełną obsługę `next dev --turbo`. -- [ ] Zaimplementuj obsługę Storybook 8.0 (przeczytaj ogłoszenie „[Storybook for React Server Components](https://storybook.js.org/blog/storybook-react-server-components)”. -- [ ] Wdrażaj możliwości współpracy, korzystając z takich rzeczy jak [liveblocks](https://liveblocks.io). -- [ ] Zaimplementuj dokumentację do projektu i przenieś każde wyjaśnienie z kodu do tego dokumentu. -- [ ] Zaimplementuj Sentry do obsługi błędów i raportów CSP dla aplikacji. -- [ ] Zaimplementuj głęboką zgodność funkcji i łatwą migrację z Reliverse. -- [ ] Zaimplementuj własną wersję [Saas UI] (<https://saas-ui.dev/>) Relivator/Reliverse, aby była w pełni kompatybilna z naszym projektem z tylko potrzebną funkcjonalnością, z użyciem Tailwind i Shadcn zamiast Chakra. -- [ ] Zaimplementuj nasz własny rozwidlenie biblioteki [Radix Themes] (<https://www.radix-ui.com/>) z skonfigurowanym `<main>` jako opakowaniem zamiast aktualnej `<sekcji>`; LUB zaimplementuj nasze własne rozwiązanie, które generuje Tailwind zamiast klas Radix. -- [ ] Implementuj funkcje AI i czat, używając na przykład [Vercel AI SDK](https://sdk.vercel.ai/docs) (patrz: [Przedstawiamy zestaw Vercel AI SDK](<https://vercel.com> /blog/przedstawiamy-vercel-ai-sdk)). -- [ ] Zaimplementuj zaawansowane przełączanie motywów bez flashowania, wykorzystując tryb ciemny Tailwind z [obsługą React Server Side](https://michaelangelo.io/blog/darkmode-rsc) i dynamicznymi plikami cookie. -- [ ] Zaimplementuj testy [Jest](https://jestjs.io), zoptymalizowane pod kątem Next.js. -- [ ] Zaimplementuj pełną obsługę [Million.js](https://million.dev) (przeczytaj [Ogłoszenie Million 3.0](https://million.dev/blog/million-3), aby dowiedzieć się więcej). -- [ ] Zaimplementuj obsługę [GraphQL](https://hygraph.com/learn/graphql) bezpieczną dla typu, używając framework [Fuse.js](https://fusejs.org). -- [ ] Zaimplementuj CLI, aby szybko uzyskać Relivator tylko z wybranymi opcjami; spróbuj użyć [Charm](https://charm.sh) rzeczy do zbudowania interfejsu CLI Reliverse. -- [ ] Gwarancja, że każda możliwa strona zostanie otoczona przy użyciu przedefiniowanych opakowań powłoki. -- [ ] Obficie komentuj cały kod, dbając o jego czystość. -- [ ] W pełni opracuj zaawansowane strony rejestracji i logowania, integrując zarówno media społecznościowe, jak i metody klasycznych formularzy. -- [ ] Postępuj zgodnie z zaleceniami [Material Design 3](https://m3.material.io) i innych systemów projektowania, jeśli ma to zastosowanie. -- [ ] Postępuj zgodnie ze sprawdzonymi metodami z artykułów i filmów, takich jak „[10 antywzorców reagowania, których należy unikać](https://www.youtube.com/watch?v=b0IZo2Aho9Y)” (sprawdź także sekcję z komentarzami). -- [ ] Ustal, udokumentuj i przestrzegaj konwencji, takich jak utrzymywanie jednego stylu nazewnictwa dla plików i zmiennych. -- [ ] Utworzenie wszechstronnego i18n, wykorzystującego kody krajów i ustawień regionalnych oraz obsługę jeszcze większej liczby języków. Upewnij się, że native speakerzy sprawdzają każdy język po tłumaczeniu maszynowym. Rozważ użycie biblioteki [next-international](https://github.com/QuiiBz/next-international). -- [ ] Wyeliminuj wszelkie wyłączenia w pliku `.eslintrc.cjs`, skonfiguruj konfigurację na ścisłą, ale nadal przyjazną dla początkujących. -- [ ] Zapewnij najwyższe bezpieczeństwo typów, używając trybu ścisłego w [TypeScript] (<https://typescriptlang.org>), typedRoutes, Zod, oprogramowaniu pośrednim itp. -- [ ] Upewnij się, że w projekcie nie ma żadnych nieużywanych elementów, w tym pakietów, bibliotek, zmiennych itp. -- [ ] Zapewnij pełną obsługę i kompatybilność Next.js Edge. -- [ ] Upewnij się, że projekt używa pętli tam, gdzie jest to naprawdę i zdecydowanie konieczne (artykuł: [Kodowanie bez pętli](https://codereadability.com/coding-without-loops)). -- [ ] Zapewnij pełną obsługę i kompatybilność [Biome](https://biomejs.dev/), [Bun](https://bun.sh) i [Docker](https://docker.com). -- [ ] Upewnij się, że wszystkie języki witryny są poprawne gramatycznie i zgodne z najnowszymi zasady dla każdego języka. -- [ ] Upewnij się, że wszystkie elementy projektu są posortowane w kolejności rosnącej, chyba że gdzie indziej wymagane jest inne sortowanie. -- [ ] Zapewnij dostępność **użytkownikom**, **programistom** (zarówno początkującym, jak i ekspertom), **botom** (takim jak [Googlebot](<https://developers.google.com/search/docs/crawling> -indexing/googlebot) lub [robot indeksujący PageSpeed Insights](https://pagespeed.web.dev)), dla **wszystkich**. -- [ ] Ulepszono konfigurację `middleware.ts` dzięki implementacji multi-middleware. -- [ ] Wykorzystaj wszystkie odpowiednie biblioteki [TanStack](https://tanstack.com). -- [ ] Elegancko skonfiguruj `app.ts`, oferując jedną konfigurację, która zastąpi wszystkie inne. -- [ ] Opracuj przepływy pracy zarówno dla sprzedawców, jak i klientów. -- [ ] Stwórz zaawansowaną witrynę sklepową zawierającą produkty, kategorie i podkategorie. -- [ ] Opracuj zaawansowaną stronę 404 Not Found z pełną obsługą internacjonalizacji. -- [ ] Opracuj zaawansowaną rejestrację, logowanie i przywracanie za pomocą hasła e-mail i magicznych linków. -- [ ] Opracuj jeszcze bardziej wyrafinowaną implementację subskrypcji użytkowników i systemu realizacji transakcji za pośrednictwem Stripe; a także napisz testy Jest/Ava dla Stripe i użyj do tych testów plików danych `.thing/hooks/stripe_*.json` [webhookthing](https://docs.webhookthing.com). -- [ ] Zmniejsz liczbę plików, łącząc podobne elementy itp. -- [ ] Stwórz możliwie najbardziej przyjazny dla początkujących i estetyczny starter. -- [ ] Utwórz zaawansowany system powiadomień, obejmujący tostery, wyskakujące okienka i strony. -- [ ] Utwórz nową stronę docelową z charakterystycznym projektem i zaktualizuj komponenty, a także całkowicie przeprojektuj wszystkie pozostałe strony i komponenty. -- [ ] Potwierdź, że projekt nie zawiera duplikatów, takich jak pliki, komponenty itp. -- [ ] Przeprowadź przydatne testy, w tym ewentualne testy warunków skrajnych, aby symulować i oceniać wydajność aplikacji w warunkach dużego ruchu. -- [ ] Kompleksowa konfiguracja routera aplikacji Next.js 14 z trasami API zarządzanymi przez moduły obsługi tras, w tym RSC i wszystkimi innymi nowymi funkcjami. -- [ ] Wypełnij listę kontrolną BA11YC (Konwencja dostępności Bleverse). -- [ ] Wypełnij części [listy kontrolnej BA11YC (Konwencja Bleverse Accessibility)](https://github.com/bs-oss/BA11YC). -- [ ] Zwiększ wyniki wydajności aplikacji na platformach takich jak Google PageSpeed Insights. Upewnij się, że aplikacja przeszła wszystkie rygorystyczne testy. -- [ ] W razie potrzeby zastosuj bibliotekę [next-usequerystate](https://github.com/47ng/next-usequerystate) ([przeczytaj artykuł](<https://francoisbest.com/posts/2023/storing-react> -stan-w-adresie-url-z-następnymi)). -- [ ] Dodaj do projektu kilka interesujących i przydatnych typów, na przykład korzystając z biblioteki [type-fest](https://github.com/sindresorhus/type-fest). -- [ ] Dodaj najcenniejsze i najbardziej przydatne rzeczy ESLint z kolekcji [awesome-eslint](https://github.com/dustinspecker/awesome-eslint). -- [ ] Dodaj wyskakujące okienka dla powiadomień o plikach cookie/RODO (z odpowiednią stroną ustawień zarządzania) i pływające powiadomienia Google umożliwiające szybkie logowanie itp. -- [ ] Dodaj panel administracyjny zawierający sklepy, produkty, zamówienia, subskrypcje i płatności. -- [ ] Dodaj zaawansowane wskaźniki zainstalowanych pakietów, zmienne środowiskowe i ulepszenia rozmiarów ekranu TailwindCSS. - -Ten plan działania przedstawia najważniejsze funkcje i ulepszenia planowane do wdrożenia w tym starterze Next.js. Elementy nieoznaczone mogą być już skonfigurowane, ale mogły nie przejść szeroko zakrojonych testów. Jeśli znajdziesz jakieś błędy, utwórz problem. - -![Zrzut ekranu strony docelowej Relivator](/public/screenshot.png) - -🌐 [https://relivator.bleverse.com](https://relivator.bleverse.com) - -## Polecenia projektu - -- **`pnpm stripe:listen`**: To polecenie uruchamia odbiornik webhook Stripe i pomaga w konfigurowaniu zmiennych środowiskowych Stripe. Aby uruchomić to polecenie, może być konieczne zainstalowanie [Stripe CLI](https://stripe.com/docs/stripe-cli). -- **`pnpm appts`**: To polecenie przeprowadza kompleksowe sprawdzenie bazy kodu. Wykonuje sekwencyjnie polecenie „pnpm lint” w celu lintingu kodu, „pnpm typecheck” w celu sprawdzenia typu i zidentyfikowania błędów TypeScript, „pnpm format” w celu sformatowania w Prettier, „pnpm test” w celu sprawdzenia testów Jest i na koniec uruchamia `kompilacja pnpm`. -- **`pnpm last`**: To polecenie aktualizuje wszystkie pakiety projektu do najnowszych stabilnych wersji i aktualizuje `next-intl` do najnowszej wersji beta/rc. -- **`pnpm up-next:canary`**: To polecenie uruchamia `pnpm najnowszy` i aktualizuje Next.js i React do najnowszych wersji dostępnych w ich oddziałach Canary. Używaj tej opcji tylko wtedy, gdy masz pewność, dlaczego jej potrzebujesz. - -## O projekcie - -Położyliśmy fundamenty — teraz Twoja kolej, aby zagłębić się w szczegóły i przyspieszyć swój rozwój. I tak, baw się dobrze — pomyśl o Relivator jak o sandbox\*\*! To jest jak Minecraft; z Relivator możesz zbudować wszystko, ponieważ Twoja kreatywność nie ma granic! Odkryj wszystko, co nowe w z Next.js 14 i wieloma rzeczami internetowymi tu i teraz — dzięki Relivator. - -Możesz nawet myśleć o Relivator jako o frameworku Next.js! Więc w końcu, po prostu to złap, pobierz i ciesz się! I nie zapominaj: Twoja opinia i gwiazdki są dla nas bardzo ważne. Naciśnij ten gwiazdkowy przycisk, prosimy! Zrób forka! Twoje zaangażowanie podnosi projekt na nowe wyżyny! Jeśli masz umiejętności kodowania, Twoje wnioski kodu są zawsze mile widziane! - -Napotkałeś problemy? Dołącz do dyskusji w naszym repozytorium, otwórz problem lub napisz do nas na DM: [Twitter/𝕏](https://x.com/blefnk), [Discord](https://discord.gg/Pb8uKbwpsJ), [Fiverr](https://fiverr.com/blefnk), [LinkedIn](https://linkedin.com/in/blefnk) lub [Facebook](https://facebook.com/blefnk). - -Ten projekt ma wielkie plany i cenimy każdą pomoc, jaką możemy uzyskać! Jeśli chcesz dokonać własnych zmian, zapoznaj się z powyższą mapą drogową projektu, aby zobaczyć potencjalne ulepszenia projektu. Użyj także `Cmd/Ctrl+Shift+F` w VSCode i wyszukaj `todo:`, aby znaleźć miejsca wymagające uwagi. Odwiedź kartę **[Zatwierdzenia](https://github.com/blefnk/relivator/issues)**, aby uzyskać więcej możliwości pomocy. - -[![Dołącz do Relivator Discord](https://discordapp.com/api/guilds/1075533942096150598/widget.png?style=banner2)][bleverse-discord] - -**🔥 Szybko się rozwijamy! Ogromne podziękowania dla [wszystkich naszych wspierających](https://github.com/blefnk/relivator/stargazers)! Sprawdź historię naszych gwiazd:** - -[![Wykres historii gwiazd](https://api.star-history.com/svg?repos=blefnk/relivator&type=Date)](https://star-history.com/#blefnk/relivator&Date) - -> **Uwaga** -> Starając się być bardzo użytecznym, ten plik README zawiera wiele informacji. Niektóre teksty mogą być nieaktualne i będą aktualizowane w miarę rozwoju. Daj nam znać na [stronie dyskusji](https://github.com/blefnk/relivator/discussions/6), jeśli zauważysz jakieś drobne problemy, takie jak nieaktualne informacje, uszkodzone linki lub błędy gramatyczne/ortograficzne w pliku README.md lub inne pliki. - -## Zmienne środowiskowe (plik `.env`) - -**Jako przewodnik użyj pliku [`.env.example`](https://github.com/blefnk/relivator/blob/main/.env.example). Po prostu skopiuj z niego dane do nowego pliku `.env`.** - -Zmienne środowiskowe `DATABASE_URL`, `NEXT_PUBLIC_DB_PROVIDER` i `NEXT_PUBLIC_AUTH_PROVIDER` są obowiązkowe; inne są opcjonalne. Możesz wdrożyć aplikację w niezmienionej postaci, ale upewnij się, że sprawdziłeś, co jest konieczne. Chociaż aplikacja będzie działać bez pewnych zmiennych, ich brak może dezaktywować określone funkcje. - -Upewnij się, że dla podstawowych zmiennych środowiskowych zdefiniowano wartości domyślne. Nigdy nie przechowuj sekretów w pliku `.env.example`. W przypadku nowicjuszy lub klonujących repo użyj `.env.example` jako szablonu do utworzenia pliku `.env`, upewniając się, że nigdy nie zostanie on zatwierdzony. Zaktualizuj schemat w `/src/env.mjs` podczas dodawania nowych zmiennych. - -Dalsze [informacje o zmiennych środowiskowych są dostępne tutaj](https://nextjs.org/docs/app/building-your-application/configuring/environment-variables). - -_Już wkrótce pojawi się czystsza i ulepszona wersja tej sekcji. Miej oko!_ - -W wersji 1.1.0 Relivator plik `.env.example` został znacznie uproszczony i będzie jeszcze bardziej usprawniony w nadchodzących wersjach. Naszym celem jest zapewnienie, że nieokreślone wartości po prostu dezaktywują powiązane funkcje bez zatrzymywania kompilacji aplikacji. - -## Płatności w paski - -Skorzystaj z pliku [`.env.example`](https://github.com/blefnk/relivator/blob/main/.env.example) jako przewodnika, gdzie i jak uzyskać wszystkie ważne klucze zmiennych środowiskowych dla Stripe , w tym webhooki zarówno dla hosta lokalnego, jak i wdrożenia. - -Lokalnie zainstaluj [Stripe CLI] (<https://stripe.com/docs/stripe-cli>) i uruchom polecenie „pnpm stripe:listen”, aby zainicjować odbiornik webhook Stripe. Ta czynność łączy Stripe z Twoim kontem i generuje klucz webhooka, który możesz następnie ustawić jako zmienną środowiskową w ustawieniach Stripe. - -Podczas testowania Stripe możesz wykorzystać jego dane testowe: `4242424242424242` | `12/34` | `567` | `Losowa nazwa` | „Przypadkowy kraj”. - -Proszę zapoznać się z plikiem [src/app/api/webhooks/stripe/route.ts](https://github.com/blefnk/relivator/blob/main/src/app/api/webhooks/stripe/route.ts) aby dowiedzieć się więcej o szczegółach działania Stripe w aplikacji. Możesz także odwiedzić [oficjalne repozytorium Stripe](https://github.com/stripe/stripe-node#readme), gdzie znajdziesz wiele przydatnych informacji. - -## Obsługa baz danych - -Relivator został zaprojektowany tak, aby bezproblemowo obsługiwać zarówno bazy danych `MySQL`, jak i `PostgreSQL`. Chociaż jest dostarczany z MySQL i [PlanetScale](https://planetscale.com) skonfigurowanymi jako domyślny dostawca bazy danych, przejście na PostgreSQL zapewniane przez [Neon](https://neon.tech)/[Vercel](https:/ /vercel.com/storage/postgres)/[Railway](https://railway.app) — to naprawdę proste jak bułka z masłem. Aby to zrobić, po prostu zaktualizuj klucz `NEXT_PUBLIC_DB_PROVIDER` w pliku `.env` do odpowiednio `neon`/`vercel`/`railway`. Chociaż Relivator jest zoptymalizowany dla tych dostawców, inne kompatybilne z Drizzle i NextAuth.js mogą również działać, chociaż mogą wymagać dodatkowej konfiguracji. - -Aby zainicjować nową bazę danych lub propagować zmiany schematu, wykonaj polecenie `pnpm mysql:push` lub `pnpm pg:push`. Dzięki temu wszystkie wersje robocze plików schematu — znajdujące się w `src/data/db/*` — zostaną odzwierciedlone w wybranym dostawcy bazy danych. - -W przypadku migracji baz danych użyj polecenia `mysql:generate`/`pg:generate`, przejrzyj folder `drizzle`, aby upewnić się, że wszystko jest w porządku (uruchom `db:drop`, jeśli nie) i uruchom komendę `pnpm:migrate`, gdy są gotowi. - -Upewnij się, że nie usuwasz ręcznie plików z katalogu `drizzle`. Jeśli konieczne jest cofnięcie migracji, użyj metody [`ppm db:drop`](https://orm.drizzle.team/kit-docs/commands#drop-migration), aby zarządzać tym w kontrolowany sposób. - -W wydaniu Relivator v1.1.0 dołożyliśmy wszelkich starań, aby zapewnić jednoczesną obsługę zarówno MySQL, jak i PostgreSQL dla Drizzle ORM. W przyszłych wersjach zamierzamy zintegrować Prisma ORM również z tym projektem. Dzięki temu projekt będzie jeszcze bardziej włączający dla wszystkich. - -Domyślnie upewniamy się, że każdy system baz danych ma wszystko to samo, używając zmiennej env `NEXT_PUBLIC_DB_PROVIDER` i eksportując rzeczy do pliku `src/data/db/index.ts`. Kiedy już zdecydujesz, który dostawca bazy danych najlepiej odpowiada Twoim potrzebom, możesz bezpiecznie skomentować lub usunąć niepotrzebnych dostawców w „skrzynce przełącznika” tego pliku, a następnie można również usunąć powiązane pliki schematu; pamiętaj, że może być również wymagana niewielka dodatkowa praca. - -### Dodatkowe uwagi na temat paska - -Trasa interfejsu API webhooka Stripe nie musi być wywoływana jawnie w aplikacji, na przykład po wybraniu przez użytkownika planu subskrypcji lub dokonaniu zakupu. Webhooki działają niezależnie od działań użytkownika na interfejsie i służą Stripe do przekazywania zdarzeń bezpośrednio do Twojego serwera. - -Gdy po stronie Stripe nastąpi zdarzenie, np. pomyślna płatność, Stripe generuje obiekt zdarzenia. Obiekt ten jest następnie automatycznie wysyłany do określonego punktu końcowego, albo w panelu kontrolnym Stripe, albo w celach testowych w pliku `package.json` poprzez interfejs CLI Stripe. Na koniec trasa API Twojego serwera odbiera zdarzenie i odpowiednio je przetwarza. - -Na przykład, gdy użytkownik wybierze plan subskrypcji, zazwyczaj najpierw użyjesz interfejsu API Stripe, aby utworzyć obiekt „Zamiar płatności” lub „Zamiar konfiguracji”. Tę akcję można wykonać po stronie klienta lub serwera. Następnie frontend potwierdza płatność za pomocą Stripe.js, kończąc w ten sposób proces konfiguracji płatności lub subskrypcji. - -Twój webhook jest automatycznie uruchamiany na podstawie tych zdarzeń. Nie ma potrzeby ręcznego „wywoływania” trasy webhooka; Stripe zarządza tym za Ciebie zgodnie z Twoimi ustawieniami w Panelu Stripe lub w pliku `package.json` na potrzeby testów lokalnych. - -Po wdrożeniu aplikacji nie zapomnij podać adresu URL elementu webhook w panelu kontrolnym Stripe. Przejdź do sekcji Webhooks i wprowadź następujący adres URL: `https://twojadomena.com/api/webhooks/stripe`. - -Podsumowując, nie ma potrzeby określania ścieżki do trasy API Stripe, w której użytkownik wybiera plan subskrypcji. Mechanizm webhooka działa niezależnie i jest uruchamiany automatycznie przez Stripe. - -## Internacjonalizacja - -_Bądź na bieżąco z dalszymi rozszerzeniami tej sekcji w przyszłości._ - -Wielojęzyczność w Bleverse jest szanowana. Uwielbiamy o tym dyskutować i planujemy zagłębić się w temat internacjonalizacji routera aplikacji Next.js 14 w przyszłych artykułach. - -Użyj `pnpm lint:i18n`, aby zweryfikować pliki i18n. Narzędzie próbuje naprawić problemy, jeśli to możliwe, oferując takie funkcje, jak sortowanie rosnąco. Brak sygnału wyjściowego oznacza, że wszystko jest w porządku. - -Obecnie wszystkie języki są tłumaczone maszynowo. Planowane są przyszłe poprawki przez native speakerów. - -Należy pamiętać, że wiadomości i18n z innego naszego projektu open source są obecnie obecne i wkrótce zostaną usunięte. - -Do internacjonalizacji używamy wersji beta/rc _next-intl_. Więcej informacji o [tutaj](https://next-intl-docs.vercel.app/blog/next-intl-3-0) i [tutaj](<https://github.com/amannn/next-intl> /pociągnij/149). - -**Obecnie obsługiwane lokalizacje (możesz dodać własne ręcznie):** - -de-DE, en-US, es-ES, fa-IR, fr-FR, hi-IN, it-IT, pl-PL, tr-TR, uk-UA. - -## Zasady, decyzje projektowe, spostrzeżenia dotyczące kodu, zalecenia - -_Nieustannie udoskonalamy tę sekcję. Wpłaty są mile widziane!_ - -Nasz starter ma być bogatym źródłem informacji dla programistów na wszystkich etapach ich podróży. W blokach komentarzy i dedykowanych sekcjach na końcu wybranych plików znajdziesz cenne spostrzeżenia i wyjaśnienia na szeroki zakres tematów. Gorąco zachęcamy do Twojego wkładu w ulepszanie tych edukacyjnych bryłek! - -**Zasady (W.I.P):** - -- [ ] Każdy plik i komponent powinien być tworzony świadomie, z pewnym poczuciem inteligencji i z myślą o wydajności. -- [ ] Musimy myśleć o projekcie tak, jakby był planetą z własnymi kontynentami, krajami, miastami, pokojami, osobami, podmiotami itp. - -**Wysoce zalecane rozszerzenia VSCode:** - -1. [Better Comments](https://marketplace.visualstudio.com/items?itemName=aaron-bond.better-comments) -2. [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) -3. [Error Lens](https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens) -4. [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) -5. [GitHub Copilot](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot) -6. [i18n Ally](https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally) -7. [JavaScript and TypeScript Nightly](https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-typescript-next) -8. [Markdown All in One](https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one) -9. [markdownlint](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint) -10. [POP! Icon Theme](https://marketplace.visualstudio.com/items?itemName=mikekscholz.pop-icon-theme) -11. [Prettier - Code formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) -12. [Pretty TypeScript Errors](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors) -13. [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) -14. [TailwindCSS Tune](https://marketplace.visualstudio.com/items?itemName=omkarbhede.tailwindcss-tune) -15. [TypeScript Essential Plugins](https://marketplace.visualstudio.com/items?itemName=zardoy.ts-essential-plugins) - -<details> - <summary>Dlaczego zalecane są „Niezbędne wtyczki TypeScript”</summary> - -«Kompletna wtyczka TypeScript, która ulepsza każdą wbudowaną funkcję, taką jak uzupełnienia, definicje, odniesienia itd., a także dodaje nawet nowe funkcje zabójcze TypeScript, dzięki czemu możesz szybciej pracować z dużymi bazami kodu! Sprawiamy, że uzupełnienia są bardziej pouczające. Definicje, odniesienia (a czasem nawet uzupełnienia) są mniej hałaśliwe. I wreszcie naszym głównym celem jest zapewnienie najbardziej konfigurowalnego środowiska TypeScript dla funkcji IDE.» © [Repozytorium rozszerzeń VSCode](https://github.com/zardoy/typescript-vscode-plugins#readme) - -Uwaga: możesz skonfigurować ustawienia rozszerzenia, otwierając interfejs użytkownika ustawień VSCode i wyszukując tam `@ext:zardoy.ts-essential-plugins`. - -</details> - -**Zaawansowane zmienne środowiskowe:** - -Plik `.env.example` zawiera wszystkie niezbędne zmienne dla w pełni funkcjonalnej strony internetowej, dostosowanej dla początkujących. Jeśli jednak potrzebujesz zaawansowanych konfiguracji, możesz w razie potrzeby zmodyfikować dowolną wartość w pliku `.env`. - -**Informacje o folderze wtyczek:** - -Ten folder zawiera opcjonalne wtyczki do Relivator. Wtyczki te, opracowane przez @blefnk i innych autorów, rozszerzają funkcjonalność i zapewniają dodatkowe funkcje. Jeśli stwierdzisz, że niektóre wtyczki nie są korzystne dla Twojego projektu, możesz usunąć odpowiadające im foldery. - -**Funkcja nad stałą dla komponentów:** - -Zalecamy używanie słowa kluczowego `function` zamiast `const` podczas definiowania komponentów React. Używanie „funkcji” często poprawia ślady stosu, ułatwiając debugowanie. Dodatkowo sprawia, że semantyka kodu jest bardziej przejrzysta, ułatwiając w ten sposób innym programistom zrozumienie Twoich intencji. - -**Osobiste rekomendacje:** - -Zalecamy regularne czyszczenie pamięci podręcznej przeglądarki i usuwanie folderu `.next`, aby zapewnić optymalną wydajność i funkcjonalność. - -Obecnie nie korzystamy z Contentlayer ze względu na jego niestabilność w systemie Windows. Dlatego badamy możliwości jego użycia w pliku konfiguracyjnym `.env`. Plany na przyszłość mogą obejmować opracowanie własnego rozwiązania do pisania treści. Integracja z dostawcami zewnętrznymi, takimi jak Sanity, może być również przyszłą funkcją, z odpowiednimi opcjami włączania/wyłączania. - -**Konfiguracja projektu:** - -Plik `src/app.ts` zawiera krytyczne konfiguracje umożliwiające modyfikację zawartości i ustawień witryny internetowej, umożliwiając: - -- Kontroluj treści prezentowane na stronie. -- Dostosuj różne ustawienia, takie jak dezaktywacja przełącznika motywu. -- Zarządzaj ogólnymi informacjami dotyczącymi całej witryny. - -Dostosuj ten plik zgodnie ze swoimi wymaganiami. - -**Uwierzytelnianie:** - -Konfigurowanie uwierzytelniania jest proste. - -Dostępnych dostawców logowania dla Clerk możesz skonfigurować w pliku `src/app.ts`. - -Pamiętaj, że Clerk w pełni współpracuje z usługami stron trzecich, takimi jak „Google PageSpeed Insight”, tylko wtedy, gdy używana jest domena i aktywne klucze. - -_Ta sekcja zostanie wkrótce wdrożona._ - -**Jak wdrożyć projekt:** - -Przed przystąpieniem do wstępnego wdrożenia sprawdź sekcję _Jak zainstalować i rozpocząć pracę_. - -Zapoznaj się z przewodnikami wdrażania dla [Vercel](https://create.t3.gg/en/deployment/vercel), [Netlify](https://create.t3.gg/en/deployment/netlify) i [Docker](https://create.t3.gg/en/deployment/docker), aby uzyskać więcej informacji. Projekt został przetestowany jedynie na Vercel; prosimy o poinformowanie nas, jeśli napotkasz problemy z innymi usługami wdrożeniowymi. - -**Projekt systemu i komponentów interfejsu użytkownika:** - -DO ZROBIENIA: Wdrożenie systemu projektowania i przewodnika po stylu. - -Domyślnie ten projekt zawiera komponenty z różnych bibliotek, a także komponenty [shadcn/ui](https://ui.shadcn.com) bez stylu. Shadcn/ui umożliwia nawet generowanie nowych komponentów interfejsu użytkownika przy użyciu interfejsu CLI (gdzie „przyciskiem” może być dowolny element interfejsu użytkownika Shadcn): `pnpm dlx shadcn-ui@latest add przycisk`. - -**Zgodność z bułkami:** - -`Relivator` może już wykorzystać kilka fantastycznych funkcji **[`Bun`](https://bun.sh)**. W przypadku tego startera obecnie zalecamy użycie `pnpm`. Pełna obsługa i kompatybilność Bun zostaną dostarczone, gdy tylko dostępne będą pliki binarne systemu Windows. _Rozbudowa sekcji już wkrótce._ - -**Typowy przepływ pracy w aplikacji (wkrótce):** - -Obszerny przewodnik szczegółowo opisujący typowe zastosowania zostanie wkrótce wdrożony. Na razie oto krótki przegląd: - -1. _Uruchom serwer deweloperski_: `ppm dev` -2. _Konfiguracja środowiska_: `.env` -3. _Konfiguracja oprogramowania pośredniego_: `middleware.ts` -4. _Dodatkowe kroki_: Bądź na bieżąco... - -**FAQ (często zadawane pytania):** - -_Q:_ Jak przyznać uprawnienia administratora sobie lub innemu użytkownikowi? -_A:_ Po prostu uruchom `pnpm db:studio`, przejdź do tabeli `acme_user` i ustaw `role: admin` dla użytkownika, którego potrzebujesz. W przyszłości, jeśli posiadasz uprawnienia administratora, będziesz mógł zmieniać uprawnienia wybranych użytkowników bezpośrednio ze strony administratora frontendu. - -_Q:_ Co oznacza zmienna środowiskowa `DEV_DEMO_NOTES`? -_A:_ Po prostu tego nie używaj. Jest używany wyłącznie na oficjalnej [witrynie demonstracyjnej Relivator](https://relivator.bleverse.com) w celu zaprezentowania pewnych funkcji, które nie są potrzebne w rzeczywistych aplikacjach. - -_P:_ Używam PlanetScale jako mojego dostawcy baz danych. Po przerwie w projekcie wyskakuje mi błąd „nie można połączyć się z oddziałem” w konsoli. Jak mogę to naprawić? -_A:_ Po prostu przejdź do panelu PlanetScale i kliknij przycisk „obudź”. Jeżeli Twoja baza danych nie śpi, a problem nadal występuje, skontaktuj się z nami. - -**Zalecane rzeczy do nauczenia:** - -1. [Introduction to Next.js and React](https://www.youtube.com/watch?v=h2BcitZPMn4) by [Lee Robinson](https://twitter.com/leeerob) -2. [Relivator: Next.js 14 Starter (Release Announce of Relivator on Medium)](https://cutt.ly/awf6fScS) by [Nazarii Korniienko @Blefnk](https://github.com/blefnk) -3. [Welcome to the Wild World of TypeScript, Mate! Is it scary?](https://cutt.ly/CwjVPUNu) by [Nazarii Korniienko @Blefnk](https://github.com/blefnk) -4. [React: Common Mistakes in 2023](https://docs.google.com/presentation/d/1kuBeSh-yTrL031IlmuwrZ8LvavOGzSbo) by [Cory House](https://twitter.com/housecor) -5. [Thoughts on Next.js 13, Server Actions, Drizzle, Neon, Clerk, and More](https://github.com/Apestein/nextflix/blob/main/README.md#overall-thoughts) by [@Apestein](https://github.com/Apestein) -6. [Huge Next-Multilingual Readme About i18n](https://github.com/Avansai/next-multilingual#readme) by [@Avansai](https://github.com/Avansai) - -_Więcej zasobów edukacyjnych można znaleźć w plikach tego repozytorium._ - -## Migracja z innych starterów do Relivator - -Jeśli zastanawiasz się, który starter Next.js wybrać do swojego następnego projektu, na przykład [create-next-app](https://vercel.com/templates/next.js/nextjs-boilerplate), [create-t3- aplikacja](https://create.t3.gg), [Next.js Commerce (Vercel Store)](https://vercel.store), [SkateShop](https://github.com/sadmann7/skateshop) , [OneStopShop](https://github.com/jackblatch/OneStopShop), [Taksonomia](https://github.com/shadcn-ui/taxonomy)/[nextflix](<https://github.com/Apestein> /nextflix), [ładunek] (<https://github.com/payloadcms/payload>), [Medusa] (<https://github.com/medusajs/medusa>) lub [dowolne inne projekty] (<https://github> .com/blefnk/relivator/wiki/Project-Credits-&-Contributors) – tutaj możesz zakończyć swoje poszukiwania. - -Wszystkie te projekty są niesamowite i jeśli przemawia do Ciebie minimalizm, polecamy je sprawdzić. Twórcy tych projektów to niezwykle utalentowane osoby, za co serdecznie im dziękujemy. Bez nich ten starter by nie istniał. - -Jednakże, **jeśli potrzebujesz POWERHOUSE** – Relivator odpowiedniego do każdego scenariusza – to **Relivator to zdecydowanie starter, którego potrzebujesz**, aby go teraz rozwidlić! Relivator zawiera wiele funkcji ze wszystkich tych projektów i stara się przesuwać granice możliwości Next.js i React. Dzięki Relivator Twoje możliwości są nieograniczone. - -Jeśli **wybierzesz Relivator jako swój następny projekt startowy** i chcesz migrować z powyższych projektów do Relivator, daj nam kilka dni. Będziemy korzystać z wiki projektu, aby napisać tam przewodnik, jak to zrobić. W tym przewodniku dowiesz się jak przenieść swój projekt do naszego projektu. Przewodnik po migracji będzie dostępny zarówno dla katalogów „aplikacja”, jak i „strony”. - -## Wkład i napisy - -_Ta sekcja zostanie wkrótce wzbogacona o prostsze kroki umożliwiające przygotowanie wszystkiego._ - -Wpłaty są mile widziane! Wyrażamy naszą wdzięczność wszystkim, którzy przyczynili się do powstania tego repozytorium. Twój wkład zostanie doceniony. Jeśli masz jakieś pytania lub sugestie, otwórz problem. Aby uzyskać więcej informacji, zobacz [przewodnik współautora](https://github.com/blefnk/relivator/blob/main/contributing.md). - -Odwiedź [tę specjalną stronę wiki](https://github.com/blefnk/relivator/wiki/Project-Credits-&-Contributors), aby wyświetlić pełną listę twórców i współautorów. Aby przyczynić się do Bleverse Relivator, wykonaj następujące kroki: - -1. Zacznij od przeczytania sekcji „Jak zainstalować i rozpocząć” na górze tego repozytorium oraz od przeczytania pliku [CONTRIBUTING.md](https://github.com/blefnk/relivator/blob/main/contributing.md) strona. -2. Utwórz gałąź: `git checkout -b <nazwa_oddziału>`. -3. Wprowadź i zatwierdź zmiany: `git commit -m '<commit_message>'` -4. Prześlij do oryginalnej gałęzi: `git Push Origin <nazwa_branży>` -5. Prześlij żądanie ściągnięcia. - -Alternatywnie przejrzyj dokumentację GitHuba na temat [jak utworzyć żądanie ściągnięcia](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request) . - -## Licencja projektu - -Ten projekt jest objęty licencją MIT i można go swobodnie wykorzystywać i modyfikować do własnych projektów. Szczegóły znajdziesz w pliku [LICENSE.md](https://github.com/blefnk/relivator/LICENSE.md). - -🌐 [https://relivator.bleverse.com](https://relivator.bleverse.com) - ---- - -**Śledź nas wszędzie:** [GitHub](https://github.com/blefnk) | [Twitter/𝕏](https://x.com/blefnk) | [Discord](https://discord.gg/Pb8uKbwpsJ) | [Fiverr](https://fiverr.com/blefnk) | [LinkedIn](https://linkedin.com/in/blefnk) | [Facebook](https://facebook.com/blefnk) - -Ten starter Next.js 14 został stworzony z miłością przez [@blefnk Nazarii Korniienko](https://github.com/blefnk) i niesamowitą [społeczność Bleverse OSS](<https://github.com/blefnk/relivator> /wiki/Project-Credits-&-Contributors). Jesteśmy głęboko wdzięczni za wszelki wkład i wsparcie udzielone przez wszystkich na rzecz tego projektu. - ---- - -Miłego kodowania! Rozpocznij swoją przygodę z kodowaniem, ucz się, iteruj i co najważniejsze – ciesz się procesem! Pamiętaj – to przestrzeń nauki i eksperymentowania. Zanurz się i rozkoszuj podróżą! 🚀🌌 - -![Obraz OG Bleverse Relivator](/public/og-image.png) - -Sprawdź [nasz inny darmowy starter Next.js 14](https://github.com/blefnk/reliverse). To, monorepo, zapewnia technologię używaną w bieżącym starterze i dodaje: Turborepo/Turbopack, Prisma, Valibot, Lucia, Clerk i wiele więcej, ponieważ eksperymentalnie próbujemy połączyć wszystkie istotne i powszechnie używane technologie. To tak jakby myśleć o: Reliverse (WordPress) + Relivator (WooCommerce) = 😍. - -[bleverse-discord]: https://discord.gg/Pb8uKbwpsJ diff --git a/.github/translations/readme/ukrainian.md b/.github/translations/readme/ukrainian.md deleted file mode 100644 index 07e3a85d..00000000 --- a/.github/translations/readme/ukrainian.md +++ /dev/null @@ -1,402 +0,0 @@ -# Relivator: Найбагатший на можливості Next.js 14 Starter - -<!-- https://github.com/blefnk/relivator#readme --> - -🌐 [Uruchom demo-wersję Relivator](https://relivator.bleverse.com) - -**Увага: Текст нижче в більшості є машинним перекладом файлу [README.md](https://github.com/blefnk/relivator#readme). Ми активно працюємо над його покращенням. Будь ласка, зверніться до оригіналу, якщо певні частини тексту виявляться незрозумілими.** - -Ми прагнемо створити найбільш функціональний і глобальний Next.js стартер у світі. Пропонуючи більше, ніж просто код — це подорож. Він стабільний і готовий до виробництва. Прокрутіть вниз і ознайомтеся із захоплюючим списком можливостей проєкту, включаючи перемикання між Clerk/NextAuth.js і MySQL/PostgreSQL на льоту. - -Щотижня ми надаємо ранній доступ до майбутніх плагінів Relivator трьом випадково обраним користувачам. Просто "відмітьте цей репозиторій" і [повідомте нам, як з вами зв'язатися] (<https://forms.gle/NXZ6QHpwrxh52VA36>). Для обговорення приєднуйтесь до [Discord проєкту](https://discord.gg/Pb8uKbwpsJ). - -## Як встановити та розпочати роботу - -1. **Основні інструменти**: Переконайтеся, що [_VSCode_](https://code.visualstudio.com), [_Git_](https://learn.microsoft.com/en-us/devops/develop/git/install-and-set-up-git), _GitHub Desktop_ ([Windows/macOS](https://desktop.github.com/) | [Linux](https://dev.to/rahedmir/is-github-desktop-available-for-gnu-linux-4a69)) та _Node.js LTS_ ([Windows/macOS](https://nodejs.org) | [Linux](https://youtu.be/NS3aTgKztis)) встановлено. -2. \*\*Клонування проєкту: [_Створити новий форк_](https://github.com/blefnk/relivator/fork) та завантажте його за допомогою GitHub Desktop. -3. **Налаштування**: Відкрийте VSCode і завантажте теку з проєктом. Натисніть `Ctrl+Shift+P` і знайдіть `>Create New Terminal`. Встановіть _PNPM_ за допомогою `corepack enable pnpm`. Потім введіть `pnpm install` для встановлення пакунків. Далі скопіюйте файл `.env.example` до нового файлу `.env` і заповніть принаймні поля `NEXT_PUBLIC_DB_PROVIDER` та `DATABASE_URL`. Нарешті, надішліть схему бази даних до вашої бази даних за допомогою `pnpm mysql:push` або `pnpm pg:push`. -4. \*\*Запуск, зупинка, збірка: Використовуйте `pnpm dev` для запуску програми (відвідайте <http://localhost:3000>, щоб перевірити це). Зупиніть його, сфокусувавшись на консолі і натиснувши `Ctrl+C`. Після внесення змін зберіть програму за допомогою `pnpm build`. Не біда, якщо ви побачите попередження Clerk. -5. \*\*Фіксація та розгортання: Завантажте ваш проєкт до вашого профілю на GitHub за допомогою GitHub Desktop. Потім розгорніть його, імпортувавши проєкт у [Vercel] (<https://vercel.com/new>), зробивши ваш веб-сайт загальнодоступним в Інтернеті. Якщо ви хочете поділитися своєю роботою, отримати відгуки або попросити про допомогу, ви можете зробити це або [на нашому сервері Discord](https://discord.gg/Pb8uKbwpsJ), або [через обговорення на GitHub](https://github.com/blefnk/relivator/discussions). - -Будь ласка, прокрутіть сторінку вниз, щоб побачити багато корисної інформації про те, як все працює в проєкті. - -## Чеклист особливостей проєкту - -Перестаньте бігати від одного стартера до іншого. З Relivator ви матимете необмежені можливості. Ви можете створити все, що завгодно; всі інструменти вже підготовлені, це все для вас. - -**Примітка:** Кожні два тижні ми даруємо ранній доступ до майбутніх плагінів Relivator трьом випадково вибраним особам. Просто поставте зірочку цьому репозиторію та [повідомте нас, як з вами зв'язатися](https://forms.gle/NXZ6QHpwrxh52VA36). Для обговорень приєднуйтесь до [Discord проєкту](https://discord.gg/Pb8uKbwpsJ). - -- [x] Використані [Next.js 14](https://nextjs.org), [React 18](https://react.dev), [TailwindCSS](https://tailwindcss.com) та [TypeScript](https://typescriptlang.org) слугують основними технологіями проєкту. -- [x] Реалізували автентифікацію за допомогою [Clerk](https://clerk.com/) та [NextAuth.js](https://authjs.dev)\*\*. -- [x] Розпочато широку інтернаціоналізацію **10 мовами** (_англійська, німецька, іспанська, перська, французька, хінді, італійська, польська, турецька, українська_), використовуючи [next-intl](https://next-intl-docs.vercel.app). -- [x] Впровадив [Drizzle ORM](https://orm.drizzle.team), використовуючи **бази даних MySQL та PostgreSQL**, а також сервіси [PlanetScale](https://planetscale.com)/[Neon](https://neon.tech)/[Vercel](https://vercel.com)/[Railway](https://railway.app). -- [x] Успішно налаштували `next.config.mjs` з підтримкою i18n та MDX. -- [x] Прагнув до ретельної документації та дружнього до новачків підходу протягом усього проєкту. -- [x] Вміло налаштував та прокоментував `middleware.ts` для i18n та next-auth. -- [x] Налаштував систему Content-Security-Policy (CSP) як захід безпеки для запобігання XSS-атакам (за замовчуванням вимкнено). -- [x] Надано зразкові налаштування VSCode та рекомендовані розширення. -- [x] Оптимізовано [Next.js Metadata API](https://nextjs.org/docs/app/building-your-application/optimizing/metadata) для SEO, інтегрувавши обробники файлової системи. -- [x] Інтегровано індикатор розміру екрану TailwindCSS для локальних запусків проєктів. -- [x] Створено систему підписки та оплати за допомогою [Stripe](hhttps://github.com/stripe/stripe-node#readme). -- [x] Забезпечили перевірку безпеки типу для схем проєкту та полів інтерфейсу за допомогою [Zod](https://zod.dev). -- [x] Використано [EsLint](https://eslint.org) та [Prettier](https://prettier.io) для забезпечення безпеки та читабельності коду. -- [x] Елегантно виконали систему шрифтів, використовуючи [Inter](https://rsms.me/inter) та додаткові шрифти. -- [x] Розробили вітрину магазину, що включає функціональність продуктів, категорій та підкатегорій. -- [x] Розробили сучасний, чітко скомпонований інтерфейс користувача з використанням [Radix](https://radix-ui.com), з привабливими компонентами інтерфейсу з [shadcn/ui](https://ui.shadcn.com). -- [x] Створено вичерпний, зручний для початківців `README.md`, включаючи описи [змінних оточення](https://nextjs.org/docs/basic-features/environment-variables). -- [x] Реалізовано функціональність блогу за допомогою файлів MDX. -- [x] Реалізовано [tRPC](https://trpc.io) та [TanStack Query](https://tanstack.com/query) (з [React Normy](https://github.com/klis87/normy#readme)) для розширеної вибірки даних на сервері та клієнті. -- [ ] Використовуйте абсолютні шляхи, де це можливо. -- [ ] Використовуйте [Kysely](https://kysely.dev) з Drizzle для досягнення повної типової безпеки конструктора SQL-запитів TypeScript. -- [ ] Перекладіть README.md та пов'язані з ним файли більшою кількістю мов. -- [ ] Перетворення з простого магазину електронної комерції на універсальний засіб для створення веб-сайтів. -- [ ] Привести до ладу `package.json` з правильно встановленими та впорядковано відсортованими пакунками у `dependencies` та `devDependencies`. -- [ ] Автору проєкту слід опублікувати серію детальних відео про те, як користуватися цим проєктом. Також мають знайтися ентузіасти, готові опублікувати власні відео про проєкт на своїх ресурсах. -- [ ] Максимально зменшити кількість пакунків проєкту, конфігураційних файлів і т.д., наскільки це можливо. -- [ ] Зменшити вкладеність HTML-тегів та забезпечити коректне використання HTML-тегів, коли це можливо. -- [ ] Надавайте пріоритет доступності як для користувача програми UI (User Interface) та UX (User Experience), так і для розробника DX (Developer Experience). Підтримуйте юзабіліті без шкоди для естетики. -- [ ] Надавайте перевагу `функціям`/`типам`, а не `константам`/`інтерфейсам`, щоб підтримувати чистий і зрозумілий для початківців код. -- [ ] Оптимізуйте всі елементи програми, щоб покращити швидкість холодного запуску та збірки. -- [ ] Мігруйте на NextAuth.js' [next-auth@beta](https://npmjs.com/package/next-auth?activeTab=versions) ([обговорення](https://github.com/nextauthjs/next-auth/releases/tag/next-auth%405.0.0-beta.4)), а також на Clerk's [@clerk/*@alpha]. -- [ ] Перемістіть кожну пов'язану систему до її спеціальної теки (до теки `src/core`), щоб будь-яку систему можна було легко вилучити з проєкту за потреби. -- [ ] Перенесіть стилі компонентів у файли .css або .scss, або скористайтеся пакетами, які надають синтаксичний цукор для стилів у файлах .tsx. -- [ ] Керуйте перевіркою електронної пошти, підпискою на розсилки та email-маркетингом за допомогою [Resend](https://resend.com) та [React Email](https://react.email). -- [ ] Зробіть кожну змінну оточення необов'язковою, щоб додаток міг працювати без жодних налаштувань, просто опускаючи певні секції коду за необхідності. -- [ ] Переконайтеся, що кожна сторінка та проміжне програмне забезпечення мають зелений або жовтий, але не червоний колір після збірки в терміналі розробки. -- [ ] Підтримуйте проєкт на найкращому з можливих способів написання хорошого і чистого коду, дотримуючись таких рекомендацій, як [Airbnb JavaScript Style Guide] (<https://github.com/airbnb/javascript/tree/master/react>) / [Airbnb React/JSX Style Guide] (<https://github.com/airbnb/javascript/tree/master/react>). -- [ ] Не допускайте в проєкті `@ts-expect-error` та пов'язаних з ним не дуже безпечних з точки зору типізації речей. -- [ ] Зменшити кількість файлів cookie до мінімуму, підготуватися до майбутнього без файлів cookie, впровадити управління файлами cookie та сповіщення. -- [ ] Впровадити систему коментарів до продуктів, включаючи типи "Відгук" і "Питання". -- [ ] Інтегрувати цінні ідеї з [Next.js Weekly] (<https://nextjsweekly.com/issues>) в цей стартер. -- [ ] Інтегрувати цінні речі з [Next.js' Examples](https://github.com/vercel/next.js/tree/canary/examples) в цей проєкт. -- [ ] Реалізувати розумну та уніфіковану систему логування, як для розробки, так і для виробництва, як для консолі, так і для запису в певні файли. -- [ ] Реалізувати найкраще з [Payload CMS](https://github.com/payloadcms/payload) з покращеннями від Relivator. -- [ ] Реалізувати завантаження файлів за допомогою [UploadThing](https://uploadthing.com) та [Cloudinary](https://cloudinary.com). -- [ ] Реалізувати повну підтримку `next dev --turbo`. -- [ ] Реалізувати динамічне перемикання між функціями програми, наприклад, провайдером бази даних, за допомогою відповідних перевірок змінних оточення. -- [ ] Реалізувати підтримку Storybook 8.0 (прочитайте анонс "[Storybook for React Server Components](https://storybook.js.org/blog/storybook-react-server-components)"). -- [ ] Реалізуйте можливості співпраці, використовуючи такі речі, як [liveblocks] (<https://liveblocks.io>). -- [ ] Додайте до проєкту документацію і перенесіть кожне пояснення з коду в цю документацію. -- [ ] Реалізувати Sentry для обробки помилок та звітів CSP для програми. -- [ ] Реалізувати глибоку сумісність функцій та легку міграцію з Reliverse. -- [ ] Реалізувати власну версію [Saas UI] (<https://saas-ui.dev/>) від Relivator/Reliverse для повної сумісності з нашим проєктом, з використанням лише необхідної функціональності, з використанням Tailwind та Shadcn замість Chakra. -- [ ] Реалізувати наш власний форк бібліотеки [Radix Themes](https://radix-ui.com/) з налаштуванням `<main>` як обгортки замість поточного `<section>`; АБО реалізувати наше власне рішення, яке генерує Tailwind замість класів Radix. -- [ ] Реалізувати функції штучного інтелекту та чату, використовуючи, наприклад, [Vercel AI SDK](https://sdk.vercel.ai/docs) (див.: [Представляємо Vercel AI SDK](https://vercel.com/blog/introducing-the-vercel-ai-sdk)). -- [ ] Реалізувати просунуте перемикання тем без перезавантаження, використовуючи Tailwind Dark Mode з підтримкою [React Server Side support](https://michaelangelo.io/blog/darkmode-rsc) та динамічних куків. -- [ ] Реалізувати тестування [Jest](https://jestjs.io), оптимізоване для Next.js. -- [ ] Реалізувати повну підтримку [Million.js](https://million.dev) (читайте [Million 3.0 Announcement](https://million.dev/blog/million-3), щоб дізнатися більше). -- [ ] Реалізувати типо-безпечну підтримку [GraphQL](https://hygraph.com/learn/graphql) за допомогою фреймворку [Fuse.js](https://fusejs.org). -- [ ] Реалізувати CLI для швидкого отримання Relivator лише з вибраними опціями; спробуйте використати [Charm](https://charm.sh) для побудови Reliverse CLI. -- [ ] Гарантувати, що кожна можлива сторінка буде обгорнута за допомогою попередньо визначених обгорток оболонки. -- [ ] Коментуйте весь код, зберігаючи його чистим. -- [ ] Повністю розробити просунуті сторінки реєстрації та входу, інтегруючи як соціальні мережі, так і класичні методи форм. -- [ ] Дотримуйтесь рекомендацій з [Material Design 3] (<https://m3.material.io>) та інших систем дизайну, коли це доречно. — Дотримуйтесь найкращих практик зі статей та відео, таких як "[10 антипатернів React, яких слід уникати] (<https://youtube.com/watch?v=b0IZo2Aho9Y>)" (також перевірте їхній розділ коментарів). -- [ ] Створіть, задокументуйте та дотримуйтесь угод, наприклад, підтримуйте єдиний стиль імен для файлів та змінних. -- [ ] Створіть повний i18n, використовуючи коди країни та місцевості, і підтримуйте ще більше мов. Переконайтеся, що носії мови перевіряють кожну мову після машинного перекладу. -- [ ] Розгляньте можливість використання бібліотеки [next-international](https://github.com/QuiiBz/next-international). -- [ ] Усуньте всі вимкнення у файлі `.eslintrc.cjs`, налаштуйте config на суворий, але дружній до початківців лад. -- [ ] Забезпечити максимальну безпеку типів, використовуючи строгий режим у [TypeScript](https://typescriptlang.org), typedRoutes, Zod, проміжному програмному забезпеченні тощо. -- [ ] Переконайтеся, що у проєкті немає невикористовуваних елементів, зокрема пакунків, бібліотек, змінних тощо. -- [ ] Переконайтеся в повній підтримці та сумісності Next.js Edge. -- [ ] Переконайтеся, що проєкт використовує цикли там, де це дійсно необхідно (стаття: [Кодування без циклів] (<https://codereadability.com/coding-without-loops>)). -- [ ] Забезпечити повну підтримку та сумісність [Biome](https://biomejs.dev/), [Bun](https://bun.sh) та [Docker](https://docker.com). -- [ ] Переконайтеся, що всі мови веб-сайту є граматично правильними та відповідають останнім правилам для кожної мови. -- [ ] Переконайтеся, що всі елементи в проєкті відсортовані за зростанням, якщо інше сортування не вимагається деінде. -- [ ] Забезпечте доступність для **користувачів**, **розробників** (як початківців, так і експертів), **ботів** (таких як [Googlebot](https://developers.google.com/search/docs/crawling-indexing/googlebot) або [PageSpeed Insights Crawler](https://pagespeed.web.dev)), для **кожного**. -- [ ] Вдосконалити конфігурацію `middleware.ts` за допомогою реалізації проміжного програмного забезпечення. -- [ ] Використовувати усі відповідні бібліотеки [TanStack](https://tanstack.com). -- [ ] Елегантно налаштувати `app.ts`, пропонуючи єдиний конфіг для заміни всіх інших. -- [ ] Розробити робочі процеси як для продавців, так і для клієнтів. -- [ ] Розробити вдосконалену вітрину магазину з продуктами, категоріями та підкатегоріями. -- [ ] Розробити вдосконалену сторінку 404 "Не знайдено" з повною підтримкою інтернаціоналізації. -- [ ] Розробити вдосконалену систему реєстрації, входу та відновлення за допомогою електронної пошти-пароля та магічних посилань. -- [ ] Розробити ще більш складну реалізацію підписок користувачів і системи оформлення замовлення через Stripe; а також написати Jest/Ava тести для Stripe і використовувати файли даних `.thing/hooks/stripe_*.json` [webhookthing](https://docs.webhookthing.com) для цих тестів. -- [ ] Зменшити кількість файлів, об'єднавши схожі елементи тощо. -- [ ] Створити максимально дружній до новачків та естетично привабливий стартовий екран. -- [ ] Створити розширену систему сповіщень, включаючи тостери, виринаючі вікна та сторінки. -- [ ] Створити нову цільову сторінку з відмінним дизайном та оновленими компонентами, а також повністю переробити всі інші сторінки та компоненти. -- [ ] Переконайтеся, що проєкт не містить дублікатів, наприклад, файлів, компонентів тощо. -- [ ] Провести корисні тести, включаючи можливі стрес-тести, щоб змоделювати та оцінити продуктивність додатку в умовах високого трафіку. -- [ ] Повністю налаштувати Next.js 14 App Router, з маршрутами API, керованими обробниками маршрутів, включаючи RSC і всі інші нові функції. -- [ ] Заповнити контрольний список BA11YC (Bleverse Accessibility Convention). -- [ ] Заповнити частини контрольного списку [BA11YC (Bleverse Accessibility Convention)] (<https://github.com/bs-oss/BA11YC>). -- [ ] Підвищити показники продуктивності застосунку на таких платформах, як Google PageSpeed Insights. Переконайтеся, що додаток пройшов усі суворі тести. -- [ ] Застосовуйте бібліотеку [next-usequerystate](https://github.com/47ng/next-usequerystate), де це доречно ([прочитайте статтю](https://francoisbest.com/posts/2023/storing-react-state-in-the-url-with-nextjs)). -- [ ] Додайте до проєкту цікаві та корисні типи, наприклад, за допомогою бібліотеки [type-fest](https://github.com/sindresorhus/type-fest). -- [ ] Додайте найцінніші та найкорисніші речі ESLint з колекції [awesome-eslint](https://github.com/dustinspecker/awesome-eslint). -- [ ] Додати виринаючі вікна для сповіщень про файли cookie/GDPR (з відповідною сторінкою налаштувань управління), а також плаваючі сповіщення Google для швидкого входу в систему тощо. -- [ ] Додати інформаційну панель адміністратора, яка включає магазини, продукти, замовлення, підписки та платежі. — [ ] Додати розширені індикатори для встановлених пакунків, змінних середовища та покращення розмірів екрану TailwindCSS. - -Ця дорожня карта описує ключові функції та покращення, заплановані для реалізації в цьому стартовому Next.js. Елементи, які не позначені, можуть бути вже налаштовані, але ще не пройшли всебічне тестування. Якщо ви знайдете будь-які помилки, будь ласка, створіть проблему. - -[!Скриншот цільової сторінки Relivator](/public/screenshot.png) - -🌐 [https://relivator.bleverse.com](https://relivator.bleverse.com) - -## Команди проєкту - -- **`pnpm stripe:listen`**: Ця команда запускає слухач веб-хуків Stripe і допомагає налаштувати змінні середовища Stripe. Для запуску цієї команди вам може знадобитися встановити [Stripe CLI](https://stripe.com/docs/stripe-cli). -- **`pnpm appts`**: Ця команда виконує комплексну перевірку кодової бази. Вона послідовно виконує `pnpm lint` для лінтування коду, `pnpm typecheck` для перевірки типів і виявлення помилок TypeScript, `pnpm format` для форматування за допомогою Prettier, `pnpm test` для перевірки Jest-тестів і, нарешті, запускає `pnpm build`. -- **`pnpm latest`**: Ця команда оновлює всі пакунки проєкту до останніх стабільних версій та оновлює `next-intl` до останньої бета/rc версії. -- **`pnpm up-next:canary`**: Ця команда запускає `pnpm latest` і оновлює Next.js і React до останніх версій, доступних у їхніх гілках canary. Використовуйте цю команду, тільки якщо ви впевнені, навіщо вам це потрібно. - -## Про проєкт - -Ми заклали фундамент — тепер ваша черга зануритися і прискорити свій розвиток. І так, розважайтеся — уявіть собі Relivator як пісочницю! Це як Minecraft: з Relivator ви можете створити все, що завгодно, адже ваша творчість не має меж! Відкрийте для себе все нове у Next.js 14 та багато веб-речей прямо тут і зараз — з Relivator. - -Ви навіть можете думати про Relivator як про фреймворк Next.js! Тож, нарешті, просто візьміть його та насолоджуйтеся! І не забувайте: ваші відгуки та зіроньки дуже важливі для нас. Тицяйте ту зіркову кнопку та зробіть форк! Ваша участь підносить проєкт на нові висоти! Якщо у вас є навички програмування, ваші внески завжди вітаються! - -Виникли проблеми? Приєднуйтесь до обговорень нашого репозиторію, відкривайте тему або пишіть нам у DM: [Twitter/𝕏](https://x.com/blefnk), [Discord](https://discord.gg/Pb8uKbwpsJ), [Fiverr](https://fiverr.com/blefnk), [LinkedIn](https://linkedin.com/in/blefnk) або [Facebook](https://facebook.com/blefnk). - -У цього проєкту великі плани, і ми цінуємо будь-яку допомогу, яку можемо отримати! Якщо ви хочете зробити свій внесок, перегляньте Дорожню карту проєкту вище, щоб побачити потенційні покращення проєкту. Також використовуйте `Cmd/Ctrl+Shift+F` у VSCode і шукайте `todo:`, щоб знайти місця, які потребують уваги. Будь ласка, відвідайте вкладку **[Commits](https://github.com/blefnk/relivator/issues)**, щоб дізнатися більше про можливості допомоги. - -[![Приєднуйтесь до Relivator Discord](https://discordapp.com/api/guilds/1075533942096150598/widget.png?style=banner2)][bleverse-discord] - -**🔥 Ми швидко зростаємо! Величезна подяка [всім нашим прихильникам](https://github.com/blefnk/relivator/stargazers)! Погляньте на нашу зоряну історію:** - -[![Star History Chart](https://api.star-history.com/svg?repos=blefnk/relivator&type=Date)](https://star-history.com/#blefnk/relivator&Date) - -**Примітка:** - -> Прагнучи бути максимально корисним, цей README містить багато інформації. Деякий текст може бути застарілим і буде оновлюватися по мірі нашого розвитку. Будь ласка, повідомте нас на [сторінці обговорення](https://github.com/blefnk/relivator/discussions/6), якщо ви помітили якісь невеликі проблеми, такі як застаріла інформація, непрацюючі посилання або граматичні/орфографічні помилки у README.md або інших файлах. - -## Змінні середовища (файл `.env`) - -**Зверніться до файлу [`.env.example`](https://github.com/blefnk/relivator/blob/main/.env.example) як до посібника. Просто скопіюйте дані з нього до нового файлу `.env`.**. - -Змінні оточення `DATABASE_URL`, `NEXT_PUBLIC_DB_PROVIDER` та `NEXT_PUBLIC_AUTH_PROVIDER` є обов'язковими; інші є необов'язковими. Ви можете розгорнути програму як є, але переконайтеся, що ви перевірили все необхідне. Хоча програма працюватиме без деяких змінних, їх відсутність може деактивувати певні функції. - -Переконайтеся, що для важливих змінних оточення визначено значення за замовчуванням. Ніколи не зберігайте секрети у файлі `.env.example`. Для новачків або репо-клонів використовуйте `.env.example` як шаблон для створення вашого файлу `.env`, переконавшись, що він ніколи не буде зафіксований. Оновлюйте схему в `/src/env.mjs` при додаванні нових змінних. - -Додаткову [інформацію про змінні оточення можна знайти тут](https://nextjs.org/docs/app/building-your-application/configuring/environment-variables). - -Незабаром буде випущено чистішу та покращену версію цього розділу. Слідкуйте за оновленнями! - -У випуску 1.1.0 Relivator було значно спрощено файл `.env.example`, який буде ще більше спрощено у наступних версіях. Ми прагнемо до того, щоб невизначені значення просто деактивували відповідну функціональність, не зупиняючи компіляцію програми. - -## Смугові платежі - -Зверніться до файлу [`.env.example`](https://github.com/blefnk/relivator/blob/main/.env.example) як до керівництва, де і як отримати всі важливі ключі змінних оточення для Stripe, включаючи веб-хуки як для localhost, так і для розгортання. - -Локально встановіть [Stripe CLI](https://stripe.com/docs/stripe-cli) і виконайте команду `pnpm stripe:listen`, щоб ініціювати слухач веб-хуків Stripe. Ця дія з'єднає Stripe з вашим акаунтом і згенерує ключ веб-хука, який ви можете встановити як змінну оточення в налаштуваннях Stripe. - -При тестуванні Stripe ви можете використовувати його тестові дані: `4242424242424242` | `12/34` | `567` | `Випадкове ім'я` | `Випадкова країна`. - -Будь ласка, зверніться до файлу [src/app/api/webhooks/stripe/route.ts](https://github.com/blefnk/relivator/blob/main/src/app/api/webhooks/stripe/route.ts), щоб дізнатися більше про те, як працює Stripe в додатку. Ви також можете відвідати [офіційний репозиторій Stripe](https://github.com/stripe/stripe-node#readme), де ви знайдете багато корисної інформації. - -## Підтримка баз даних - -Relivator розроблено для легкої підтримки баз даних `MySQL` та `PostgreSQL`. Хоча він постачається з MySQL та [PlanetScale](https://planetscale.com), налаштованими як постачальники баз даних за замовчуванням, перехід на PostgreSQL від [Neon](https://neon.tech)/[Vercel](https://vercel.com/storage/postgres)/[Railway](https://railway.app) — дуже простий, як пиріг. Для цього просто змініть ключ `NEXT_PUBLIC_DB_PROVIDER` у вашому файлі `.env` на `neon`/`vercel`/`railway` відповідно. Хоча Relivator оптимізовано для цих провайдерів, будь-які інші, сумісні з Drizzle та NextAuth.js, також можуть працювати, хоча й потребують додаткового налаштування. - -Щоб ініціювати нову базу даних або поширити зміни схеми, виконайте команду `pnpm mysql:push` або `pnpm pg:push`. Це гарантує, що всі чернетки, зроблені у файлах схеми, які знаходяться у каталозі `rc/data/db/*`, будуть віддзеркалені у вибраному вами провайдері баз даних. - -Для перенесення бази даних скористайтеся командами `mysql:generate`/`pg:generate`, перегляньте теку `drizzle`, щоб переконатися, що все зроблено правильно (якщо ні, виконайте `db:drop`), і виконайте команду `pnpm:migrate`, коли будете готові. - -Переконайтеся, що ви не видаляєте файли з каталогу `drizzle` вручну. Якщо міграцію потрібно скасувати, скористайтеся командою [`pnpm db:drop`](https://orm.drizzle.team/kit-docs/commands#drop-migration), щоб зробити це контрольованим способом. - -У випуску Relivator v1.1.0 ми доклали максимум зусиль, щоб забезпечити одночасну підтримку MySQL та PostgreSQL для Drizzle ORM. У майбутніх релізах ми плануємо інтегрувати Prisma ORM до цього проєкту. Завдяки цьому проєкт стане ще більш інклюзивним для всіх. - -За замовчуванням ми гарантуємо, що кожна система баз даних має все однаково, використовуючи змінну `NEXT_PUBLIC_DB_PROVIDER` env та експортуючи дані у файлі `src/data/db/index.ts`. Коли ви вирішите, який провайдер бази даних найкраще відповідає вашим потребам, ви можете безпечно закоментувати або видалити непотрібних провайдерів у `witch-case` цього файлу, після чого також можна видалити відповідні файли схем; зауважте, що може знадобитися невелика додаткова робота. - -### Додаткові примітки про Stripe - -Маршрут API веб-хука Stripe не обов'язково викликати явно у вашому додатку, наприклад, після того, як користувач вибрав план підписки або здійснив покупку. Веб-хуки працюють незалежно від дій користувача на фронтенді і слугують засобом, за допомогою якого Stripe передає події безпосередньо на ваш сервер. - -Коли на стороні Stripe відбувається подія, наприклад, успішна оплата, Stripe генерує об'єкт події. Потім цей об'єкт автоматично надсилається до кінцевої точки, яку ви вказали, або в панелі інструментів Stripe, або, для тестування, у вашому `package.json` через інтерфейс командного рядка Stripe CLI. Нарешті, маршрут API вашого сервера отримує подію і обробляє її відповідним чином. - -Наприклад, коли користувач обирає план підписки, ви зазвичай спочатку використовуєте API Stripe для створення об'єкта `Payment Intent` або `Setup Intent`. Ця дія може бути виконана як на стороні клієнта, так і на стороні сервера. Потім фронтенд підтверджує платіж за допомогою Stripe.js, тим самим завершуючи процес оплати або налаштування підписки. - -Ваш веб-хук автоматично запускається на основі цих подій. Немає необхідності вручну "викликати" маршрут веб-хука; Stripe керує цим за вас відповідно до ваших налаштувань в панелі інструментів Stripe або в вашому `package.json` для локального тестування. - -Після розгортання вашого додатку не забудьте вказати URL-адресу веб-хука у вашому Stripe Dashboard. Перейдіть до розділу Webhooks і введіть наступну URL-адресу: `https://yourdomain.com/api/webhooks/stripe`. - -Таким чином, немає необхідності вказувати шлях до вашого маршруту API Stripe, де користувач обирає план підписки. Механізм веб-хуків працює незалежно і запускається Stripe автоматично. - -## Інтернаціоналізація - -Слідкуйте за подальшим розширенням цього розділу в майбутньому. - -Багатомовність у Bleverse шанується. Ми обожнюємо обговорювати її і плануємо заглибитися в тему інтернаціоналізації Next.js 14 App Router в майбутніх статтях. - -Використовуйте `pnpm lint:i18n` для перевірки ваших i18n файлів. Інструмент намагається виправити проблеми, коли це можливо, пропонуючи такі функції, як сортування за зростанням. Відсутність результатів означає, що все в порядку. - -Наразі всі мови перекладено машинним перекладом. В майбутньому планується доопрацювання носіями мови. - -Зверніть увагу, що наразі присутні повідомлення i18n з іншого нашого проєкту з відкритим вихідним кодом, які буде видалено найближчим часом. - -Для інтернаціоналізації ми використовуємо бета/rc _next-intl_ версії. Додаткову інформацію про них можна знайти [тут](https://next-intl-docs.vercel.app/blog/next-intl-3-0) і [тут](https://github.com/amannn/next-intl/pull/149). - -**Підтримувані локалі (ви можете додати власну вручну):**: de-DE, en-US, en, en, en, en, en, en, en, en, en, en, en, en, en - -de-DE, en-US, es-ES, fa-IR, fr-FR, hi-IN, it-IT, pl-PL, tr-TR, uk-UA. - -## Принципи, дизайнерські рішення, ідеї коду, рекомендації - -Ми постійно вдосконалюємо цей розділ. Ми вітаємо ваші пропозиції! - -Наш стартер має на меті стати багатим ресурсом для розробників на всіх етапах їхньої подорожі. У блоках коментарів та спеціальних розділах в кінці окремих файлів ви знайдете цінні поради та роз'яснення з широкого спектру тем. Ваш внесок у покращення цих освітніх скарбів буде дуже доречним! - -**Принципи (W.I.P.):** - -- Кожен файл і компонент повинен бути створений свідомо, з певним почуттям інтелекту, з думкою про продуктивність. -- Ми повинні думати про проєкт, як про планету зі своїми континентами, країнами, містами, кімнатами, людьми, організаціями тощо. - -**Наполегливо рекомендовані розширення VSCode:**. - -1. [Better Comments](https://marketplace.visualstudio.com/items?itemName=aaron-bond.better-comments) -2. [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) -3. [Error Lens](https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens) -4. [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) -5. [GitHub Copilot](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot) -6. [i18n Ally](https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally) -7. [JavaScript and TypeScript Nightly](https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-typescript-next) -8. [Markdown All in One](https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one) -9. [markdownlint](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint) -10. [POP! Icon Theme](https://marketplace.visualstudio.com/items?itemName=mikekscholz.pop-icon-theme) -11. [Prettier — Code formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) -12. [Pretty TypeScript Errors](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors) -13. [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) -14. [TailwindCSS Tune](https://marketplace.visualstudio.com/items?itemName=omkarbhede.tailwindcss-tune) -15. [TypeScript Essential Plugins](https://marketplace.visualstudio.com/items?itemName=zardoy.ts-essential-plugins) - -<details> - <summary>Чому рекомендується "TypeScript Essential Plugins"</summary> - -"Повнофункціональний плагін TypeScript, який покращує кожну вбудовану функцію, таку як завершення, визначення, посилання тощо, а також додає нові функції-кілери TypeScript, щоб ви могли швидше працювати з великими кодовими базами! Ми зробили доповнення більш інформативними. Визначення, посилання (а іноді і завершення) — менш шумними. І, нарешті, наша головна мета — забезпечити максимально адаптований досвід роботи з TypeScript для функцій IDE". © [VSCode Extension Repository](https://github.com/zardoy/typescript-vscode-plugins#readme) - -Примітка: Ви можете налаштувати параметри розширення, відкривши інтерфейс налаштувань VSCode і знайшовши там `@ext:zardoy.ts-essential-plugins`. - -</details> - -**Розширені змінні оточення:** - -Файл `.env.example` містить всі основні змінні для повнофункціонального веб-сайту, призначеного для початківців. Однак, якщо вам потрібні розширені конфігурації, ви можете змінити будь-яке значення у файлі `.env` за потреби. - -Про теку плагінів:\*_*Про теку плагінів:*_ - -Ця тека містить додаткові плагіни для Relivator. Розроблені @blefnk та іншими учасниками, ці плагіни розширюють функціональність і надають додаткові можливості. Якщо ви вважаєте, що певні плагіни не є корисними для вашого проєкту, не соромтеся видаляти відповідні теки. - -**Перевизначення функцій над константами для компонентів:** - -Ми рекомендуємо використовувати ключове слово `function` замість `const` при визначенні React-компонентів. Використання `function` часто покращує трасування стеку, що полегшує налагодження. Крім того, це робить семантику коду більш явною, тим самим полегшуючи іншим розробникам розуміння ваших намірів. - -**Особисті рекомендації:** - -Ми рекомендуємо регулярно очищати кеш браузера і видаляти теку `.next` для забезпечення оптимальної продуктивності і функціональності. - -Наразі ми не використовуємо Contentlayer через його нестабільну роботу з Windows. Тому ми вивчаємо можливості його використання в конфігураційному файлі `.env`. В майбутньому ми плануємо розробити власне рішення для написання контенту. Інтеграція із зовнішніми провайдерами, такими як Sanity, також може стати майбутньою функцією, з відповідними опціями ввімкнення/вимкнення. - -**Конфігурація проєкту:** - -У файлі `src/app.ts` містяться критичні конфігурації для зміни вмісту та налаштувань веб-сайту, що дозволяє вам - -- Керувати вмістом, представленим на веб-сайті. -- Налаштовувати різні параметри, наприклад, деактивувати перемикач тем. -- Керувати загальною інформацією на сайті. - -Налаштувати цей файл відповідно до ваших вимог. - -**Автентифікація:** - -Налаштування автентифікації дуже просте. - -Ви можете налаштувати доступних постачальників послуг для входу в Clerk у файлі `src/app.ts`. - -Будь ласка, пам'ятайте, що Clerk повноцінно працює зі сторонніми сервісами, такими як "Google PageSpeed Insight", лише за умови використання доменних і живих ключів. - -Цей розділ буде реалізовано найближчим часом. - -**Як розгорнути проєкт:** - -Будь ласка, ознайомтеся з розділом _Як встановити і почати роботу_ перед початковим розгортанням. - -Зверніться до посібників з розгортання для [Vercel](https://create.t3.gg/en/deployment/vercel), [Netlify](https://create.t3.gg/en/deployment/netlify) та [Docker](https://create.t3.gg/en/deployment/docker) для отримання більш детальної інформації. Проєкт було протестовано лише на Vercel; будь ласка, повідомте нас, якщо у вас виникнуть проблеми з іншими сервісами розгортання. - -**Система дизайну та компоненти інтерфейсу користувача:** - -TODO: Реалізувати систему дизайну та керівництво по стилю. - -За замовчуванням цей проєкт включає компоненти з різних бібліотек, а також нестилізовані [shadcn/ui](https://ui.shadcn.com) компоненти. Shadcn/ui навіть дозволяє генерувати нові компоненти інтерфейсу користувача за допомогою свого CLI (де "кнопкою" може бути будь-який елемент інтерфейсу користувача Shadcn): `pnpm dlx shadcn-ui@latest add button`. - -**Сумісність з Bun Things:** - -`Relivator` вже може використовувати деякі фантастичні можливості **[`Bun`](https://bun.sh)**. Для початку ми рекомендуємо використовувати `pnpm`. Повну підтримку та сумісність з Bun буде надано, щойно будуть доступні двійкові файли для Windows. Розширення розділу незабаром. - -**Типові робочі процеси програм (незабаром):** - -Вичерпний посібник, що детально описує типовий робочий процес програми, буде реалізовано найближчим часом. Наразі, ось короткий огляд: - -1. Запуск сервера розробки: `pnpm dev`. -2. Конфігурація оточення: `.env` 2. -3. Налаштування проміжного програмного забезпечення\_: `middleware.ts`. -4. Додаткові кроки: Слідкуйте за новинами... - -**FAQ (Часті запитання):** - -_П:_ Як надати права адміністратора собі або іншому користувачеві? -_В:_ Просто запустіть `pnpm db:studio`, перейдіть до таблиці `acme_user` і встановіть `role: admin` для потрібного вам користувача. В майбутньому, якщо у вас є права адміністратора, ви зможете змінювати права обраних користувачів безпосередньо зі сторінки адміністратора фронтенду. - -_П:_ Що означає змінна оточення `DEV_DEMO_NOTES`? -_В:_ Просто не використовуйте її. Вона використовується лише на офіційному [демо-сайті Relivator](https://relivator.bleverse.com) для демонстрації певних можливостей, які не потрібні у реальних програмах. - -_П:_ Я використовую PlanetScale як провайдера баз даних. Після перерви в роботі над проєктом я зіткнувся з помилкою "unable to connect to branch" в консолі. Як я можу це виправити? -_В:_ Просто перейдіть на інформаційну панель PlanetScale і натисніть на кнопку "прокинутися". Будь ласка, зв'яжіться з нами у випадку, якщо ваша база даних не заснула, а проблема не зникла. - -**Рекомендовані речі, які варто вивчити:** - -1. [Introduction to Next.js and React](https://youtube.com/watch?v=h2BcitZPMn4) by [Lee Robinson](https://twitter.com/leeerob) -2. [Relivator: Next.js 14 Starter (Release Announce of Relivator on Medium)](https://cutt.ly/awf6fScS) by [Nazarii Korniienko @Blefnk](https://github.com/blefnk) -3. [Welcome to the Wild World of TypeScript, Mate! Is it scary?](https://cutt.ly/CwjVPUNu) by [Nazarii Korniienko @Blefnk](https://github.com/blefnk) -4. [React: Common Mistakes in 2023](https://docs.google.com/presentation/d/1kuBeSh-yTrL031IlmuwrZ8LvavOGzSbo) by [Cory House](https://twitter.com/housecor) -5. [Thoughts on Next.js 13, Server Actions, Drizzle, Neon, Clerk, and More](https://github.com/Apestein/nextflix/blob/main/README.md#overall-thoughts) by [@Apestein](https://github.com/Apestein) -6. [Huge Next-Multilingual Readme About i18n](https://github.com/Avansai/next-multilingual#readme) by [@Avansai](https://github.com/Avansai) - -Більше навчальних ресурсів можна знайти у файлах цього сховища. - -## Міграція з інших стартерів до Relivator - -Якщо ви в роздумах, який Next.js стартер вибрати для вашого наступного проєкту, як [create-next-app](https://vercel.com/templates/next.js/nextjs-boilerplate), [create-t3-app](https://create.t3.gg), [Next.js Commerce (Vercel Store)](https://vercel.store), [SkateShop](https://github.com/sadmann7/skateshop), [OneStopShop](https://github.com/jackblatch/OneStopShop), [Taxonomy](https://github.com/shadcn-ui/taxonomy)/[nextflix](https://github.com/Apestein/nextflix), [payload](https://github.com/payloadcms/payload), [Medusa](https://github.com/medusajs/medusa) чи [інші проєкти](https://github.com/blefnk/relivator/wiki/Project-Credits-&-Contributors) – ваш пошук може закінчитися саме на Relivator. - -Всі ці проєкти неймовірні, і якщо вам подобається мінімалізм, ми рекомендуємо ознайомитися з ними. Творці цих проєктів — надзвичайно талановиті люди, і ми висловлюємо їм безмежну подяку. Без них цієї добірки не існувало б. - -Однак, якщо вам потрібне щось що не має жодних обмежень — Relivator, придатний для будь-якого сценарію — тоді **Relivator — це саме той стартап, який вам потрібен**, щоб запустити його прямо зараз! Relivator включає в себе численні функції з усіх цих проєктів і прагне розширити межі можливостей Next.js та React. З Relivator ваші можливості безмежні. - -Якщо ви **вибираєте Relivator як свій наступний проєкт** і хочете перейти з вищезгаданих проєктів на Relivator, будь ласка, дайте нам кілька днів. Ми використаємо вікі проєкту, щоб написати посібник про те, як це зробити. У цьому посібнику ви дізнаєтеся, як перенести ваш проєкт на наш проєкт. Посібник з міграції буде доступний для каталогів "app" і "pages". - -## Внески та кредити - -Незабаром цей розділ буде доповнено простішими кроками, які допоможуть вам підготувати все необхідне. - -Ми щиро вітаємо внески! Ми висловлюємо подяку всім, хто долучився до створення цього сховища. Ваш внесок буде визнано. Якщо у вас є запитання чи пропозиції, будь ласка, відкрийте тему. Для отримання додаткової інформації див. [посібник для дописувачів] (<https://github.com/blefnk/relivator/blob/main/contributing.md>). - -Будь ласка, відвідайте [цю спеціальну вікі-сторінку](https://github.com/blefnk/relivator/wiki/Project-Credits-&-Contributors), щоб переглянути повний список авторів та дописувачів. Щоб зробити внесок у Bleverse Relivator, виконайте наступні кроки: - -1. Почніть з прочитання розділу "Як встановити і почати роботу" у верхній частині цього репозиторію, а також сторінки [CONTRIBUTING.md](https://github.com/blefnk/relivator/blob/main/contributing.md). -2. Створіть гілку: `git checkout -b <назва_гілки>`. -3. Внесіть і зафіксуйте ваші зміни: `git commit -m '<повідомлення_фіксації>'`. -4. Перенесіть до початкової гілки: `git push origin <назва_гілки>' -5. Надішліть запит на вилучення. - -Крім того, зверніться до документації GitHub про те, як створити запит на вилучення (<https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request>). - -## Ліцензія проєкту - -Цей проєкт має ліцензію MIT і є вільним для використання та модифікації для ваших власних проєктів. Перевірте файл [LICENSE.md](https://github.com/blefnk/relivator/LICENSE.md) для отримання детальної інформації. - -🌐 [https://relivator.bleverse.com](https://relivator.bleverse.com) - ---- - -**Слідкуйте за нами всюди:** [GitHub](https://github.com/blefnk) | [Twitter/𝕏](https://x.com/blefnk) | [Discord](https://discord.gg/Pb8uKbwpsJ) | [Fiverr](https://fiverr.com/blefnk) | [LinkedIn](https://linkedin.com/in/blefnk) | [Facebook](https://facebook.com/blefnk) - -Цей стартер Next.js 14 був створений з любов'ю [@blefnk Назарієм Корнієнком](https://github.com/blefnk) та неймовірною [спільнотою Bleverse OSS](https://github.com/blefnk/relivator/wiki/Project-Credits-&-Contributors). Ми глибоко вдячні всім за внесок і підтримку, надану для цього проєкту. - ---- - -Щасливого кодування! Вирушайте у свою пригоду з кодування, вчіться, ітерації, а головне — отримуйте задоволення від процесу! Пам'ятайте — це простір навчання та експериментів. Поринь і насолоджуйся подорожжю! 🚀🌌 - -![Bleverse Relivator OG Image](/public/og-image.png) - -Перевірте [наш інший безкоштовний початковий курс Next.js 14](https://github.com/blefnk/reliverse). Цей, монорепо, надає технологію, що використовується в поточному стартері, і додає нові можливості: Turborepo/Turbopack, Prisma, Valibot, Lucia, Clerk і багато іншого, оскільки ми експериментально намагаємося об'єднати всі життєво важливі і широко використовувані технології. Це як думати про: Reliverse (WordPress) + Relivator (WooCommerce) = 😍. - -[bleverse-discord]: https://discord.gg/Pb8uKbwpsJ diff --git a/.github/workflows/RELIVERSE.yml b/.github/workflows/RELIVERSE.yml new file mode 100644 index 00000000..b017d7ae --- /dev/null +++ b/.github/workflows/RELIVERSE.yml @@ -0,0 +1,21 @@ +name: Reliverse Standard +on: + push: null +jobs: + build: + runs-on: ubuntu-22.04 + strategy: + matrix: + node-version: + - 20 + steps: + - uses: actions/checkout@v4 + - name: Install pnpm + uses: pnpm/action-setup@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: pnpm + - name: Install dependencies + run: pnpm install --no-frozen-lockfile diff --git a/.github/workflows/STARGAZERS.yml b/.github/workflows/STARGAZERS.yml new file mode 100644 index 00000000..c436f380 --- /dev/null +++ b/.github/workflows/STARGAZERS.yml @@ -0,0 +1,58 @@ +name: Stargazers +on: + workflow_dispatch: + inputs: + repoOrg: + description: Repo Org + required: true + default: blefnk + repoName: + description: Repo Name + required: true + default: relivator-nextjs-template + starCount: + description: Star Count + required: true + default: "900" + duration: + description: Duration (seconds) + required: false + default: "15" +jobs: + render: + name: Stargazers + runs-on: ubuntu-22.04 + strategy: + matrix: + node-version: + - 20 + steps: + - name: Run checkout + uses: actions/checkout@v4 + - name: Install pnpm + uses: pnpm/action-setup@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: pnpm + - name: Install Fallback fonts (fonts) + run: | + sudo apt-get update -yqq + sudo apt-get install -yq --no-install-recommends \ + fonts-noto-core fonts-noto-cjk fonts-noto-color-emoji fonts-noto-mono + - name: Install dependencies from package.json + run: pnpm install --no-frozen-lockfile + - name: Define input props + run: echo $WORKFLOW_INPUT | tee input-props.json | jq -C '.' + env: + WORKFLOW_INPUT: ${{ toJson(github.event.inputs) }} + - name: Stargazers + run: pnpm stargazers:build -- --props="./input-props.json" + env: + REMOTION_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload video + uses: actions/upload-artifact@v3 + with: + name: stargazers.mp4 + path: public/internal/stargazers.mp4 diff --git a/.gitignore b/.gitignore index 825a1993..3106bcae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,48 +1,168 @@ -# @see https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files +# See https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files -# dependencies -/node_modules -/.pnp +# Dependency directories +node_modules/ +jspm_packages/ +bower_components +web_modules/ +.pnp .pnp.js -# testing -/coverage +# Build outputs +build/ +dist/ +out/ +.next/ +.nuxt/ +.gatsby/ +target/ -# next.js -/.next/ -/out/ -next-env.d.ts - -# production -/build - -# misc -.DS_Store -*.pem - -# debug +# Logs +logs/ +*.log npm-debug.log* yarn-debug.log* yarn-error.log* -*.ignore.* -*.ignore +lerna-debug.log* +.pnpm-debug.log* +yarn-error.log* + +# Runtime data +pids/ +*.pid +*.seed +*.pid.lock + +# Coverage directories +lib-cov/ +coverage/ +*.lcov +.nyc_output + +# Grunt intermediate storage +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons +build/Release + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn files +.yarn/ +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* +.yarn-integrity -# local env files -# do not commit any .env files to git -# except for the .env.example file -.env*.local +# dotenv environment variable files .env +.env.* +.env.local +__.env +.__.env -# vercel +# Parcel cache +.cache +.parcel-cache + +# Expo +.expo/ +dist/ +expo-env.d.ts +apps/expo/.gitignore + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Turbo +.turbo + +# Vercel .vercel -# postbuild -robots.txt -sitemap.xml -sitemap-*.xml -*.tsbuildinfo -/.swc +# Diagnostic reports +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json -# logs -.logs -*.log +# IntelliJ based IDEs +.idea + +# MacOS Finder +.DS_Store + +# Binaries +*.exe +*.dll +*.so +*.dylib +*.bin + +# Temporary files +*.tmp +*.temp +*.bak +*.old +*.orig +*.swp + +# Python related +__pycache__/ +*.py[oc] +*.egg-info +.venv +.ruff_cache +.wheels/ + +# Code editors +*.sublime-project +*.sublime-workspace +*.idea/ +*.vscode/ +.eslintcache +.vscode-test + +# Misc +*.pem +*.swc +path_mapping.txt + +# Reliverse +addons/.output +.million +yarn-error.log +coverage diff --git a/.knip.json b/.knip.json deleted file mode 100644 index d5436725..00000000 --- a/.knip.json +++ /dev/null @@ -1,101 +0,0 @@ -{ - "$schema": "https://unpkg.com/knip@2/schema.json", - "paths": { "~/*": ["src/*"] }, - "entry": [ - "src/env.mjs", - "next.config.{js,ts,cjs,mjs}", - "drizzle.config.ts", - "src/middleware.{js,ts}", - "src/{app,pages}/**/*.{js,jsx,ts,tsx}" - ], - "project": ["src/**/*.{js,jsx,ts,tsx}"], - "ignore": ["src/{data,hooks,islands,}/**/*.{ts,tsx}", "src/i18n.ts"], - "ignoreBinaries": ["email", "stripe", "next-intl@3.0.0-rc.7"], - "ignoreDependencies": [ - "@auth/core", - "@auth/drizzle-adapter", - "@clerk/localizations", - "@clerk/nextjs", - "@clerk/themes", - "@clerk/types", - "@faker-js/faker", - "@hookform/resolvers", - "@loglib/tracker", - "@neondatabase/serverless", - "@planetscale/database", - "@radix-ui/react-accordion", - "@radix-ui/react-alert-dialog", - "@radix-ui/react-aspect-ratio", - "@radix-ui/react-avatar", - "@radix-ui/react-checkbox", - "@radix-ui/react-dialog", - "@radix-ui/react-dropdown-menu", - "@radix-ui/react-icons", - "@radix-ui/react-label", - "@radix-ui/react-menubar", - "@radix-ui/react-navigation-menu", - "@radix-ui/react-popover", - "@radix-ui/react-scroll-area", - "@radix-ui/react-select", - "@radix-ui/react-separator", - "@radix-ui/react-slider", - "@radix-ui/react-slot", - "@radix-ui/react-switch", - "@radix-ui/react-tabs", - "@radix-ui/react-toast", - "@radix-ui/react-tooltip", - "@stripe/react-stripe-js", - "@stripe/stripe-js", - "@tanstack/react-query", - "@tanstack/react-table", - "@trpc/client", - "@trpc/react-query", - "@trpc/server", - "@types/lodash", - "@types/mdx", - "@types/pg", - "@uploadthing/react", - "@vercel/analytics", - "axios", - "class-variance-authority", - "clsx", - "cmdk", - "cropperjs", - "cspell", - "dayjs", - "drizzle-zod", - "embla-carousel-react", - "eslint-config-next", - "eslint-interactive", - "flag-icons", - "international-types", - "lodash", - "lucide-react", - "mdx", - "micro", - "mysql2", - "next-auth", - "next-international", - "next-themes", - "nextjs-google-analytics", - "nodemailer", - "pg", - "postgres", - "react-cropper", - "react-day-picker", - "react-dropzone", - "react-hook-form", - "react-hot-toast", - "react-icons", - "react-medium-image-zoom", - "react-wrap-balancer", - "request-ip", - "stripe", - "superjson", - "tailwind-merge", - "tailwind-variants", - "ts-deepmerge", - "uploadthing", - "vaul" - ] -} diff --git a/.lintstagedrc.json b/.lintstagedrc.json deleted file mode 100644 index 3f744e72..00000000 --- a/.lintstagedrc.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "**/*.{js,jsx,cjs,mjs,ts,tsx,md,mdx}": [ - "eslint --fix", - "prettier --check --write" - ], - "**/*.{json,yml}": ["prettier --check --write"], - "**/*.css": ["prettier --write"] -} diff --git a/.npmcheckrc b/.npmcheckrc deleted file mode 100644 index 6b8f612d..00000000 --- a/.npmcheckrc +++ /dev/null @@ -1,34 +0,0 @@ -{ - "depcheck": { - "ignoreMatches": [ - "~", - "@clerk/localizations", - "@clerk/nextjs", - "@clerk/themes", - "@clerk/types", - "@cspell/dict-companies", - "@cspell/dict-de-de", - "@cspell/dict-es-es", - "@cspell/dict-fr-fr", - "@cspell/dict-fullstack", - "@cspell/dict-it-it", - "@cspell/dict-markdown", - "@cspell/dict-npm", - "@cspell/dict-pl_pl", - "@cspell/dict-tr-tr", - "@cspell/dict-typescript", - "@cspell/dict-uk-ua", - "@tanstack/react-query", - "@types/node", - "autoprefixer", - "cspell", - "dotenv", - "flag-icons", - "drizzle.config", - "eslint-interactive", - "international-types", - "next-international", - "postcss" - ] - } -} diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..41583e36 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@jsr:registry=https://npm.jsr.io diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 4d2021b6..00000000 --- a/.prettierignore +++ /dev/null @@ -1,18 +0,0 @@ -# @see https://prettier.io/docs/en/ignore.html - -# dependencies -/node_modules - -# next.js -/.next -/out - -# production -/build -/dist - -# typescript -next-env.d.ts - -# project -/drizzle diff --git a/.putout.json b/.putout.json new file mode 100644 index 00000000..632b65d1 --- /dev/null +++ b/.putout.json @@ -0,0 +1,732 @@ +{ + "parser": "babel", + "printer": "putout", + "formatter": [ + "progress-bar", + { + "minCount": 10 + } + ], + "processors": [ + ["javascript", "on"], + ["json", "on"], + ["markdown", "on"], + ["ignore", "on"], + ["yaml", "on"], + ["css", "off"], + ["filesystem", "on"] + ], + "match": { + "*.{mjs,ts,tsx,mts}": { + "nodejs/remove-useless-strict-mode": "on", + "nodejs/add-missing-strict-mode": "off" + }, + "*.{jsx,js,cjs}": { + "nodejs/add-missing-strict-mode": "off", + "nodejs/remove-useless-strict-mode": "on" + }, + "{.,}putout.json": { + "putout-config": "on" + }, + ".madrun.{js,cjs,mjs}": { + "madrun": "on" + }, + "setupTests.*": { + "remove-empty/import": "off" + }, + ".filesystem.json": { + "filesystem": "on", + "nodejs/cjs-file": "on", + "nodejs/mjs-file": "on", + "nodejs/rename-file-cjs-to-js": "on", + "nodejs/rename-file-mjs-to-js": "on", + "package-json/find-file": "on", + "typescript": "on", + "typescript/cts-file": "on", + "typescript/mts-file": "on", + "typescript/rename-file-cts-to-ts": "on", + "typescript/rename-file-mts-to-ts": "on" + }, + "*.md": { + "convert-assignment-to-declaration": "off", + "conditions/apply-consistent-blocks": "off", + "conditions/convert-comparison-to-boolean": "off", + "conditions/remove-constant": "off", + "maybe": "off", + "convert-quotes-to-backticks": "off", + "remove-unused-expressions": "off", + "remove-unused-variables": "off", + "remove-useless-escape": "off", + "remove-useless-variables": "off", + "remove-useless-return": "off", + "remove-empty": "off", + "for-of/remove-unused-variables": "off", + "remove-console": "off", + "remove-unreachable-code": "off", + "declare": "off", + "nodejs/declare": "off" + }, + "*.svelte": { + "remove-unused-variables": "off" + }, + "{*rc,*.{json,yml}}": { + "convert-quotes-to-backticks": "off", + "remove-useless-escape": "off", + "math/apply-numeric-separators": "off" + }, + ".github/**/*.yml": { + "github": "on", + "github/set-node-versions": [ + "off", + { + "versions": ["18.x", "20.x", "22.x"] + } + ], + "remove-useless-escape": "off" + }, + ".gitignore": { + "gitignore": "on" + }, + ".npmignore": { + "npmignore": "on" + }, + ".{nyc,c8}rc.json": { + "coverage": "on" + }, + ".*ignore": { + "convert-quotes-to-backticks": "off" + }, + "webpack.config.js": { + "webpack": "on" + }, + "browserlist": { + ".browserlistrc": "on" + }, + ".eslintrc{*,.json}": { + "eslint": "on", + "eslint/convert-require-to-import": "off", + "eslint/apply-match-to-flat": "off" + }, + "eslint.config.*": { + "eslint": "on", + "putout/convert-match-to-function": "off" + }, + "package.json": { + "package-json": "on" + }, + "bin": { + "nodejs/remove-process-exit": "off", + "nodejs/convert-top-level-return": "on", + "remove-console": "off", + "remove-empty/import": "off" + }, + "{test,*.spec.{js,mjs,cjs}}": { + "tape": "on" + }, + "*.mjs": { + "nodejs/convert-commonjs-to-esm": "on", + "tape/convert-mock-require-to-mock-import": "on" + }, + "*.mts": { + "typescript/convert-commonjs-to-esm": "on" + }, + "*.cts": { + "typescript/convert-esm-to-commonjs": "on" + }, + "*.cjs": { + "nodejs/convert-esm-to-commonjs": "on" + }, + "*.{ts,tsx,mts,cts,md{ts},md{tsx}}": { + "typescript": "on" + }, + "*.d.ts": { + "declare": "off" + } + }, + "ignore": [ + "**/.git", + "**/.idea", + "**/.next", + "**/.nyc_output", + "**/.pnp.*", + "**/.venv", + "**/.yarn", + "**/*.gif", + "**/*.jpeg", + "**/*.lock", + "**/*.png", + "**/build", + "**/dist", + "**/next-env.d.ts", + "**/node_modules", + "**/package-lock.json", + "**/pnpm-lock.yaml", + "**/python", + "**/reset.d.ts", + "**/yarn-error.log", + "addons/.output", + "addons/cluster/reliverse/ui/cluster-readme.tsx", + "addons/reliverse/academy/fileHandler.ts", + "addons/reliverse/academy/leaders.ts", + "addons/reliverse/relicon/setup/prompt/appts.ts", + "addons/reliverse/relicon/setup/prompt/canary/json.ts", + "addons/reliverse/relimter/core/env/components/MissingVariables.tsx", + "addons/reliverse/relimter/putout/tasks/index.ts", + "src/app/[locale]/blog/new/**/*.tsx", + "src/components/Application/ReliverseToolbar.tsx", + "src/components/Common/loading-button.tsx", + "src/components/Common/password-input.tsx", + "src/components/Navigation/SiteHeader.tsx", + "src/components/primitives", + "src/components/Providers/AuthjsClerkProvider.tsx", + "src/data/other/countries/typescript/inputMask.ts", + "src/db", + "src/server/media.ts", + "src/server/string.ts", + "src/utils/errors/helpers/field.ts", + "src/utils/id.ts" + ], + "rules": { + "apply-at": "on", + "apply-destructuring/array": "on", + "apply-destructuring/falsy": "on", + "apply-destructuring/object": "on", + "apply-montag": "on", + "apply-optional-chaining/assign": "off", + "apply-optional-chaining/use": "off", + "apply-overrides": "on", + "browserlist": "on", + "browserlist/remove-node-versions": "on", + "conditions/add-return": "on", + "conditions/apply-comparison-order": "on", + "conditions/apply-consistent-blocks": "off", + "conditions/apply-if": "on", + "conditions/convert-comparison-to-boolean": "on", + "conditions/convert-equal-to-strict-equal": "on", + "conditions/evaluate": "on", + "conditions/merge-if-statements": "on", + "conditions/remove-boolean": "off", + "conditions/remove-constant": "off", + "conditions/remove-same-values-condition": "off", + "conditions/remove-useless-else": "off", + "conditions/remove-zero": "off", + "conditions/simplify": "on", + "convert-array-copy-to-slice": "off", + "convert-optional-to-logical/call": "off", + "convert-quotes-to-backticks": "off", + "convert-template-to-string": "on", + "convert-to-arrow-function": "on", + "coverage": "off", + "eslint": "on", + "filesystem": "on", + "for-of/for-each": "on", + "for-of/map": "on", + "for-of/reduce": "on", + "github": "on", + "github/add-continue-on-error-to-add-and-commit": "on", + "github/add-continue-on-error-to-coveralls": "on", + "github/convert-npm-to-bun": "off", + "github/install-bun": "off", + "github/install-rust": "off", + "github/set-node-versions": "off", + "github/update-actions": "on", + "gitignore": "on", + "group-imports-by-source": "off", + "typescript/apply-as-type-assertion": "on", + "typescript/apply-type-guards": "off", + "typescript/apply-utility-types": "on", + "typescript/convert-commonjs-to-esm": "off", + "typescript/convert-esm-to-commonjs": "off", + "typescript/convert-generic-to-shorthand": "on", + "typescript/cts-file": "on", + "typescript/mts-file": "on", + "typescript/remove-duplicates-exports": "off", + "typescript/remove-duplicates-from-union": "off", + "typescript/remove-duplicates-interface-keys": "off", + "typescript/remove-getter-arguments": "off", + "typescript/remove-setter-return-type": "off", + "typescript/remove-unused-types": "off", + "typescript/remove-useless-mapped-types": "off", + "typescript/remove-useless-parens": "off", + "typescript/remove-useless-promise": "off", + "typescript/remove-useless-types-from-constants": "off", + "typescript/remove-useless-types": "off", + "typescript/rename-file-cts-to-ts": "on", + "typescript/rename-file-mts-to-ts": "on", + "typescript/find-file": [ + "on", + { + "ignore": [] + } + ], + "convert-apply-to-spread": "on", + "convert-arguments-to-rest": "on", + "convert-assignment-to-declaration": "on", + "convert-concat-to-flat": "on", + "convert-index-of-to-includes": "on", + "declare-before-reference": "on", + "declare-imports-first": "on", + "declare": "on", + "eslint/add-putout": "off", + "eslint/apply-dir-to-flat": "on", + "eslint/apply-match-to-flat": "on", + "eslint/apply-safe-align": "on", + "eslint/convert-export-match-to-declaration": "on", + "eslint/convert-files-to-array": "on", + "eslint/convert-ide-to-safe": "on", + "eslint/convert-node-to-n": "on", + "eslint/convert-require-to-import": "on", + "eslint/declare": "on", + "eslint/move-putout-to-end-of-extends": "on", + "eslint/remove-no-missing": "off", + "eslint/remove-no-unpublished-require": "off", + "logical-expressions/convert-bitwise-to-logical": "on", + "logical-expressions/remove-boolean": "off", + "logical-expressions/remove-duplicates": "off", + "logical-expressions/simplify": "on", + "madrun": "off", + "math/apply-numeric-separators": "off", + "math/apply-exponentiation": "on", + "montag/apply": "on", + "new/add-missing": "on", + "new/remove-useless": "off", + "nodejs/cjs-file": "off", + "nodejs/convert-commonjs-to-esm": "off", + "nodejs/convert-esm-to-commonjs": "off", + "nodejs/mjs-file": "off", + "nodejs/rename-file-cjs-to-js": "off", + "promises/add-missing-async": "on", + "promises/add-missing-await": "on", + "promises/apply-await-import": "on", + "putout/add-await-to-progress": "on", + "putout/add-index-to-import": "on", + "putout/add-places-to-compare-places": "on", + "putout/add-test-args": "on", + "putout/add-track-file": "on", + "putout/add-traverse-args": "on", + "putout/apply-async-formatter": "on", + "putout/apply-create-test": "on", + "putout/apply-declare": "on", + "putout/apply-for-of-to-track-file": "on", + "putout/apply-insert-after": "on", + "putout/apply-insert-before": "on", + "putout/apply-namespace-specifier": "on", + "putout/apply-processors-destructuring": "on", + "putout/apply-remove": "off", + "putout/apply-rename": "on", + "putout/apply-short-processors": "on", + "putout/check-match": "on", + "remove-empty/block": "off", + "remove-empty/static-block": "off", + "remove-nested-blocks": "off", + "simplify-assignment": "on", + "webpack/apply-externals": "on", + "webpack/convert-loader-to-use": "on", + "webpack/convert-node-to-resolve-fallback": "on", + "webpack/convert-query-loader-to-use": "on", + "putout/check-replace-code": [ + "on", + { + "once": true + } + ], + "filesystem/bundle": "off", + "filesystem/remove-vim-swap-file": "on", + "madrun/add-fix-lint": "off", + "madrun/add-function": "off", + "madrun/add-run": "off", + "putout/convert-add-argument-to-add-args": "on", + "putout/convert-babel-types": "on", + "putout/convert-destructuring-to-identifier": "on", + "putout/convert-dirname-to-url": "off", + "putout/convert-get-rule-to-require": "on", + "putout/convert-match-to-function": "on", + "putout/convert-method-to-property": "on", + "putout/convert-node-to-path-in-get-template-values": "on", + "putout/convert-number-to-numeric": "on", + "putout/convert-process-to-find": "on", + "putout/convert-progress-to-track-file": "on", + "putout/convert-putout-test-to-create-test": "on", + "putout/convert-replace-to-function": "on", + "putout/convert-replace-with-multiple": "on", + "putout/convert-replace-with": "on", + "putout/convert-report-to-function": "on", + "putout/convert-to-no-transform-code": "on", + "putout/convert-traverse-to-include": "on", + "putout/convert-traverse-to-replace": "on", + "putout/convert-traverse-to-scan": "on", + "putout/convert-url-to-dirname": "on", + "putout/create-test": "on", + "filesystem/read-all-files": [ + "off", + { + "mask": "*" + } + ], + "extract-object-properties/equal-deep": "on", + "extract-object-properties/not-equal-deep": "on", + "extract-sequence-expressions": "on", + "filesystem/convert-simple-filesystem-to-filesystem": "off", + "filesystem/move-referenced-file": "off", + "filesystem/remove-files": "off", + "filesystem/rename-file": "off", + "filesystem/rename-referenced-file": "off", + "filesystem/rename-spec-to-test": "off", + "filesystem/rename-test-to-spec": "off", + "filesystem/write-all-files": "off", + "reuse-duplicate-init": "on", + "filesystem/replace-cwd": [ + "off", + { + "from": "/home/relivator-nextjs-template", + "to": "/" + } + ], + "filesystem/convert-json-to-js": [ + "off", + { + "filename": "package.json" + } + ], + "filesystem/convert-js-to-json": [ + "off", + { + "filename": "package.js" + } + ], + "for-of/add-missing-declaration": "on", + "for-of/for-in": "on", + "for-of/remove-unused-variables": "off", + "for-of/remove-useless-array-from": "off", + "for-of/remove-useless": "off", + "madrun/add-cut-env": "off", + "for-of/remove-useless-variables": [ + "off", + { + "maxProperties": 4 + } + ], + "eslint/convert-plugins-array-to-object": "off", + "eslint/convert-rc-to-flat": "on", + "eslint/remove-no-unsupported-features": "off", + "eslint/remove-overrides-with-empty-rules": "off", + "eslint/remove-useless-slice": "off", + "for-of/for": "on", + "generators/add-missing-star": "on", + "generators/convert-multiple-to-generator": "on", + "madrun/call-run": "off", + "madrun/convert-args-to-scripts": "off", + "madrun/convert-cut-env-to-run": "off", + "madrun/convert-nyc-to-c8": "off", + "madrun/convert-run-argument": "off", + "madrun/convert-run-to-cut-env": "off", + "madrun/convert-to-async": "off", + "madrun/remove-check-duplicates-from-test": "off", + "madrun/remove-useless-array-in-run": "off", + "madrun/remove-useless-string-conversion": "off", + "madrun/rename-eslint-to-putout": "off", + "madrun/rename-series-to-run": "off", + "madrun/set-lint-dot": "off", + "madrun/set-report-lcov": "off", + "math/apply-multiplication": "on", + "math/convert-sqrt-to-hypot": "on", + "math/declare": "on", + "nodejs/add-node-prefix": "off", + "nodejs/rename-file-mjs-to-js": "off", + "package-json/add-type": "on", + "package-json/find-file": "on", + "package-json/remove-commit-type": "off", + "tape/apply-stub": "off", + "tape/jest": "off", + "package-json/remove-nyc": "on", + "promises/apply-top-level-await": "on", + "promises/apply-with-resolvers": "on", + "promises/convert-new-promise-to-async": "on", + "promises/convert-reject-to-throw": "on", + "promises/remove-useless-async": "off", + "promises/remove-useless-await": "off", + "promises/remove-useless-resolve": "off", + "promises/remove-useless-variables": "off", + "putout-config/convert-boolean-to-string": "on", + "putout-config/move-formatter-up": "on", + "putout-config/remove-empty": "off", + "putout/declare": "off", + "putout/includer": "on", + "putout/move-require-on-top-level": "on", + "putout/remove-empty-array-from-process": "off", + "putout/remove-unused-get-properties-argument": "off", + "putout/replace-test-message": "on", + "putout/shorten-imports": "on", + "putout/simplify-replace-template": "on", + "types/apply-is-array": "on", + "types/convert-typeof-to-istype": "off", + "types/declare": "on", + "types/remove-double-negations": "off", + "types/remove-useless-conversion": "off", + "types/remove-useless-typeof": "off", + "npmignore": [ + "off", + { + "dismiss": [ + ".nyc_output", + ".putoutcache", + "*.swp", + "coverage", + "*.config.*" + ] + } + ], + "maybe/array": "on", + "maybe/declare": "on", + "maybe/empty-array": "on", + "maybe/fn": "on", + "maybe/noop": "off", + "montag/declare": "on", + "nodejs/add-missing-strict-mode": "on", + "nodejs/convert-buffer-to-buffer-alloc": "on", + "nodejs/convert-dirname-to-url": "off", + "nodejs/convert-fs-promises": "on", + "nodejs/convert-promisify-to-fs-promises": "on", + "nodejs/convert-top-level-return": "on", + "nodejs/convert-url-to-dirname": "on", + "nodejs/declare-after-require": "on", + "nodejs/declare": "off", + "nodejs/remove-process-exit": "off", + "nodejs/remove-useless-promisify": "off", + "nodejs/remove-useless-strict-mode": "off", + "regexp/apply-ends-with": "on", + "regexp/apply-literal-notation": "on", + "regexp/apply-starts-with": "on", + "regexp/convert-replace-to-replace-all": "on", + "regexp/convert-to-string": "on", + "regexp/optimize": "on", + "regexp/remove-useless-group": "off", + "regexp/remove-useless-regexp": "off", + "remove-console": "off", + "remove-debugger": "off", + "remove-duplicate-case": "off", + "remove-duplicate-keys": "off", + "remove-empty/argument": "off", + "remove-empty/export": "off", + "remove-empty/nested-pattern": "off", + "remove-empty/pattern": "off", + "remove-iife": "off", + "remove-unreachable-code": "off", + "remove-unreferenced-variables": "off", + "remove-unused-expressions": "off", + "remove-unused-private-fields": "off", + "remove-unused-variables": "off", + "remove-useless-arguments/arguments": "off", + "remove-useless-arguments/destructuring": "off", + "remove-useless-arguments/method": "off", + "remove-useless-array-constructor": "off", + "remove-useless-array-entries": "off", + "remove-useless-array": "off", + "remove-useless-assign": "off", + "remove-useless-continue": "off", + "remove-useless-escape": "off", + "remove-useless-functions": "off", + "remove-useless-map": "off", + "remove-useless-operand": "off", + "remove-useless-replace": "off", + "remove-useless-return": "off", + "remove-useless-spread/array": "off", + "remove-useless-spread/nested": "off", + "remove-useless-spread/object": "off", + "remove-useless-template-expressions": "off", + "remove-useless-variables/await": "off", + "remove-useless-variables/declaration": "off", + "remove-useless-variables/destruct": "off", + "remove-useless-variables/duplicate": "off", + "remove-useless-variables/remove": "off", + "remove-useless-variables/rename": "off", + "simplify-boolean-return": "on", + "simplify-ternary/spread": "on", + "simplify-ternary/value": "on", + "try-catch/args": "on", + "try-catch/async": "on", + "try-catch/await": "on", + "try-catch/declare": "on", + "try-catch/expand-arguments": "on", + "try-catch/sync": "on", + "remove-empty/import": [ + "off", + { + "ignore": [] + } + ], + "apply-dot-notation": "on", + "apply-early-return": "on", + "apply-flat-map": "on", + "apply-nullish-coalescing": "off", + "apply-starts-with": "on", + "apply-template-literals": "on", + "convert-assignment-to-arrow-function": "on", + "convert-assignment-to-comparison": "on", + "convert-const-to-let": "on", + "convert-label-to-object": "on", + "convert-object-assign-to-merge-spread": "on", + "convert-object-entries-to-array-entries": "on", + "convert-optional-to-logical/assign": "on", + "convert-throw": "on", + "merge-destructuring-properties": "on", + "merge-duplicate-functions": "on", + "merge-duplicate-imports/join": "off", + "merge-duplicate-imports/rename": "off", + "nodejs/convert-exports-to-module-exports": "on", + "package-json": "on", + "putout-config": "on", + "putout": "on", + "sort-imports-by-specifiers": "off", + "split-assignment-expressions": "on", + "split-nested-destructuring": "on", + "split-variable-declarations": "on", + "tape": "on", + "types/convert-typeof-to-is-type": "off", + "typescript": "on", + "webpack": "on", + "apply-shorthand-properties": [ + "off", + { + "ignore": [] + } + ], + "convert-assert-to-with": "on", + "convert-is-nan-to-number-is-nan": "on", + "nextjs/convert-page-to-head": "on", + "nextjs/create-app-directory": "on", + "nextjs/move-404-to-not-found": "on", + "nextjs/remove-a-from-link": "off", + "nextjs/update-tsconfig-file": "on", + "nextjs/update-tsconfig": "off", + "react-hook-form/v5-remove-value-from-control": "off", + "react-hook-form/v6-apply-clear-errors": "on", + "react-hook-form/v6-convert-as-to-render": "on", + "react-hook-form/v6-convert-form-context-to-form-provider": "on", + "react-hook-form/v6-convert-trigger-validation-to-trigger": "on", + "react-hook-form/v7-apply-form-state": "on", + "react-hooks/apply-short-fragment": "on", + "react-hooks/convert-class-to-function": "on", + "react-hooks/convert-component-to-use-state": "on", + "react-hooks/convert-import-component-to-use-state": "on", + "react-hooks/convert-state-to-hooks": "on", + "react-hooks/declare": "on", + "react-hooks/remove-bind": "off", + "react-hooks/remove-react": "off", + "react-hooks/remove-this": "off", + "react-hooks/rename-method-under-score": "on", + "react/apply-create-root": "off", + "react/remove-implicit-ref-return": "off", + "react/remove-useless-forward-ref": "off", + "react/remove-useless-provider": "off" + }, + "plugins": [ + "apply-at", + "apply-destructuring", + "apply-dot-notation", + "apply-early-return", + "apply-flat-map", + "apply-montag", + "apply-nullish-coalescing", + "apply-optional-chaining", + "apply-overrides", + "apply-shorthand-properties", + "apply-starts-with", + "apply-template-literals", + "browserlist", + "conditions", + "convert-apply-to-spread", + "convert-arguments-to-rest", + "convert-array-copy-to-slice", + "convert-assert-to-with", + "convert-assignment-to-arrow-function", + "convert-assignment-to-comparison", + "convert-assignment-to-declaration", + "convert-concat-to-flat", + "convert-const-to-let", + "convert-index-of-to-includes", + "convert-is-nan-to-number-is-nan", + "convert-label-to-object", + "convert-object-assign-to-merge-spread", + "convert-object-entries-to-array-entries", + "convert-optional-to-logical", + "convert-quotes-to-backticks", + "convert-template-to-string", + "convert-throw", + "convert-to-arrow-function", + "coverage", + "declare-before-reference", + "declare-imports-first", + "declare", + "eslint", + "extract-object-properties", + "extract-sequence-expressions", + "filesystem", + "for-of", + "generators", + "github", + "gitignore", + "group-imports-by-source", + "logical-expressions", + "madrun", + "math", + "maybe", + "merge-destructuring-properties", + "merge-duplicate-functions", + "merge-duplicate-imports", + "montag", + "new", + "nextjs", + "nodejs", + "npmignore", + "package-json", + "promises", + "putout-config", + "putout", + "react-hook-form", + "react-hooks", + "react", + "regexp", + "remove-console", + "remove-debugger", + "remove-duplicate-case", + "remove-duplicate-keys", + "remove-empty", + "remove-iife", + "remove-nested-blocks", + "remove-unreachable-code", + "remove-unreferenced-variables", + "remove-unused-expressions", + "remove-unused-private-fields", + "remove-unused-variables", + "remove-useless-arguments", + "remove-useless-array-constructor", + "remove-useless-array-entries", + "remove-useless-array", + "remove-useless-assign", + "remove-useless-constructor", + "remove-useless-continue", + "remove-useless-escape", + "remove-useless-functions", + "remove-useless-map", + "remove-useless-operand", + "remove-useless-replace", + "remove-useless-return", + "remove-useless-spread", + "remove-useless-template-expressions", + "remove-useless-variables", + "reuse-duplicate-init", + "simplify-assignment", + "simplify-boolean-return", + "simplify-ternary", + "sort-imports-by-specifiers", + "split-assignment-expressions", + "split-nested-destructuring", + "split-variable-declarations", + "tape", + "try-catch", + "types", + "typescript", + "webpack" + ] +} diff --git a/.storybook/main.ts b/.storybook/main.ts deleted file mode 100644 index 59e458c6..00000000 --- a/.storybook/main.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @see https://github.com/shilman/storybook-rsc-demo - * @see https://storybook.js.org/blog/storybook-react-server-components - */ - -import type { StorybookConfig } from "@storybook/nextjs"; - -const config: StorybookConfig = { - stories: [], - staticDirs: ["../src/public"], - framework: { name: "@storybook/nextjs", options: {} }, -}; - -export default config; diff --git a/.swcrc b/.swcrc deleted file mode 100644 index de00279f..00000000 --- a/.swcrc +++ /dev/null @@ -1,36 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/swcrc", - "minify": true, - "env": { - "mode": "entry", - "coreJs": "3.27", - "targets": { - "chrome": "64", - "edge": "79", - "firefox": "67", - "opera": "51", - "safari": "12", - "ios": "12" - } - }, - "jsc": { - "transform": { "react": { "runtime": "automatic" } }, - "parser": { - "jsx": false, - "decoratorsBeforeExport": false, - "exportNamespaceFrom": false, - "exportDefaultFrom": false, - "dynamicImport": false, - "decorators": false, - "importMeta": false, - "functionBind": false, - "privateMethod": false, - "syntax": "ecmascript", - "topLevelAwait": false - }, - "externalHelpers": true, - "keepClassNames": false, - "target": "esnext", - "loose": true - } -} diff --git a/.thing/hooks/discord_webhook.json b/.thing/hooks/discord_webhook.json deleted file mode 100644 index 55c966e2..00000000 --- a/.thing/hooks/discord_webhook.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "content": "Hey, welcome to webhookthing The easiest way to test webhooks on your local dev environment!\n_ _", - "embeds": [ - { - "title": "What's this?", - "description": "This is a sample discord embed", - "color": 5814783, - "fields": [ - { - "name": "This is a field", - "value": "This is the field's value" - } - ], - "author": { - "name": "This is the Author", - "url": "https://example.com", - "icon_url": "https://via.placeholder.com/64" - }, - "footer": { - "text": "This is a footer", - "icon_url": "https://via.placeholder.com/8" - }, - "timestamp": "1970-01-01T08:00:00.000Z", - "image": { - "url": "https://via.placeholder.com/512" - }, - "thumbnail": { - "url": "https://via.placeholder.com/64" - } - } - ], - "username": "example#0001", - "avatar_url": "https://via.placeholder.com/32", - "attachments": [] -} diff --git a/.thing/hooks/github_pr_opened.json b/.thing/hooks/github_pr_opened.json deleted file mode 100644 index 7a6a23c3..00000000 --- a/.thing/hooks/github_pr_opened.json +++ /dev/null @@ -1,532 +0,0 @@ -{ - "action": "opened", - "number": 15, - "pull_request": { - "url": "https://api.github.com/repos/example_org/example_repo/pulls/15", - "id": 1211243938, - "node_id": "PR_0000000000000000", - "html_url": "https://github.com/example_org/example_repo/pull/15", - "diff_url": "https://github.com/example_org/example_repo/pull/15.diff", - "patch_url": "https://github.com/example_org/example_repo/pull/15.patch", - "issue_url": "https://api.github.com/repos/example_org/example_repo/issues/15", - "number": 15, - "state": "open", - "locked": false, - "title": "jdoe/example-branch", - "user": { - "login": "jdoe", - "id": 1234567, - "node_id": "MDQ6VXNlcjEyMzQ1Njc=", - "avatar_url": "https://avatars.githubusercontent.com/u/1234567?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/jdoe", - "html_url": "https://github.com/jdoe", - "followers_url": "https://api.github.com/users/jdoe/followers", - "following_url": "https://api.github.com/users/jdoe/following{/other_user}", - "gists_url": "https://api.github.com/users/jdoe/gists{/gist_id}", - "starred_url": "https://api.github.com/users/jdoe/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/jdoe/subscriptions", - "organizations_url": "https://api.github.com/users/jdoe/orgs", - "repos_url": "https://api.github.com/users/jdoe/repos", - "events_url": "https://api.github.com/users/jdoe/events{/privacy}", - "received_events_url": "https://api.github.com/users/jdoe/received_events", - "type": "User", - "site_admin": false - }, - "body": "This is a test PR", - "created_at": "2023-01-20T09:03:04Z", - "updated_at": "2023-01-20T09:03:04Z", - "closed_at": null, - "merged_at": null, - "merge_commit_sha": null, - "assignee": null, - "assignees": [], - "requested_reviewers": [], - "requested_teams": [], - "labels": [], - "milestone": null, - "draft": false, - "commits_url": "https://api.github.com/repos/example_org/example_repo/pulls/15/commits", - "review_comments_url": "https://api.github.com/repos/example_org/example_repo/pulls/15/comments", - "review_comment_url": "https://api.github.com/repos/example_org/example_repo/pulls/comments{/number}", - "comments_url": "https://api.github.com/repos/example_org/example_repo/issues/15/comments", - "statuses_url": "https://api.github.com/repos/example_org/example_repo/statuses/07a6048532c799c58bf7eafdbc7d4eaf6b6bbde6", - "head": { - "label": "example_org:jdoe/example-branch", - "ref": "jdoe/example-branch", - "sha": "07a6048532c799c58bf7eafdbc7d4eaf6b6bbde6", - "user": { - "login": "example_org", - "id": 2345678, - "node_id": "MDEyOk9yZ2FuaXphdGlvbjIzNDU2Nzg=", - "avatar_url": "https://avatars.githubusercontent.com/u/2345678?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/example_org", - "html_url": "https://github.com/example_org", - "followers_url": "https://api.github.com/users/example_org/followers", - "following_url": "https://api.github.com/users/example_org/following{/other_user}", - "gists_url": "https://api.github.com/users/example_org/gists{/gist_id}", - "starred_url": "https://api.github.com/users/example_org/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/example_org/subscriptions", - "organizations_url": "https://api.github.com/users/example_org/orgs", - "repos_url": "https://api.github.com/users/example_org/repos", - "events_url": "https://api.github.com/users/example_org/events{/privacy}", - "received_events_url": "https://api.github.com/users/example_org/received_events", - "type": "Organization", - "site_admin": false - }, - "repo": { - "id": 553972582, - "node_id": "R_kgDOIQTzZg", - "name": "example_repo", - "full_name": "example_org/example_repo", - "private": true, - "owner": { - "login": "example_org", - "id": 2345678, - "node_id": "MDEyOk9yZ2FuaXphdGlvbjIzNDU2Nzg=", - "avatar_url": "https://avatars.githubusercontent.com/u/2345678?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/example_org", - "html_url": "https://github.com/example_org", - "followers_url": "https://api.github.com/users/example_org/followers", - "following_url": "https://api.github.com/users/example_org/following{/other_user}", - "gists_url": "https://api.github.com/users/example_org/gists{/gist_id}", - "starred_url": "https://api.github.com/users/example_org/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/example_org/subscriptions", - "organizations_url": "https://api.github.com/users/example_org/orgs", - "repos_url": "https://api.github.com/users/example_org/repos", - "events_url": "https://api.github.com/users/example_org/events{/privacy}", - "received_events_url": "https://api.github.com/users/example_org/received_events", - "type": "Organization", - "site_admin": false - }, - "html_url": "https://github.com/example_org/example_repo", - "description": null, - "fork": false, - "url": "https://api.github.com/repos/example_org/example_repo", - "forks_url": "https://api.github.com/repos/example_org/example_repo/forks", - "keys_url": "https://api.github.com/repos/example_org/example_repo/keys{/key_id}", - "collaborators_url": "https://api.github.com/repos/example_org/example_repo/collaborators{/collaborator}", - "teams_url": "https://api.github.com/repos/example_org/example_repo/teams", - "hooks_url": "https://api.github.com/repos/example_org/example_repo/hooks", - "issue_events_url": "https://api.github.com/repos/example_org/example_repo/issues/events{/number}", - "events_url": "https://api.github.com/repos/example_org/example_repo/events", - "assignees_url": "https://api.github.com/repos/example_org/example_repo/assignees{/user}", - "branches_url": "https://api.github.com/repos/example_org/example_repo/branches{/branch}", - "tags_url": "https://api.github.com/repos/example_org/example_repo/tags", - "blobs_url": "https://api.github.com/repos/example_org/example_repo/git/blobs{/sha}", - "git_tags_url": "https://api.github.com/repos/example_org/example_repo/git/tags{/sha}", - "git_refs_url": "https://api.github.com/repos/example_org/example_repo/git/refs{/sha}", - "trees_url": "https://api.github.com/repos/example_org/example_repo/git/trees{/sha}", - "statuses_url": "https://api.github.com/repos/example_org/example_repo/statuses/{sha}", - "languages_url": "https://api.github.com/repos/example_org/example_repo/languages", - "stargazers_url": "https://api.github.com/repos/example_org/example_repo/stargazers", - "contributors_url": "https://api.github.com/repos/example_org/example_repo/contributors", - "subscribers_url": "https://api.github.com/repos/example_org/example_repo/subscribers", - "subscription_url": "https://api.github.com/repos/example_org/example_repo/subscription", - "commits_url": "https://api.github.com/repos/example_org/example_repo/commits{/sha}", - "git_commits_url": "https://api.github.com/repos/example_org/example_repo/git/commits{/sha}", - "comments_url": "https://api.github.com/repos/example_org/example_repo/comments{/number}", - "issue_comment_url": "https://api.github.com/repos/example_org/example_repo/issues/comments{/number}", - "contents_url": "https://api.github.com/repos/example_org/example_repo/contents/{+path}", - "compare_url": "https://api.github.com/repos/example_org/example_repo/compare/{base}...{head}", - "merges_url": "https://api.github.com/repos/example_org/example_repo/merges", - "archive_url": "https://api.github.com/repos/example_org/example_repo/{archive_format}{/ref}", - "downloads_url": "https://api.github.com/repos/example_org/example_repo/downloads", - "issues_url": "https://api.github.com/repos/example_org/example_repo/issues{/number}", - "pulls_url": "https://api.github.com/repos/example_org/example_repo/pulls{/number}", - "milestones_url": "https://api.github.com/repos/example_org/example_repo/milestones{/number}", - "notifications_url": "https://api.github.com/repos/example_org/example_repo/notifications{?since,all,participating}", - "labels_url": "https://api.github.com/repos/example_org/example_repo/labels{/name}", - "releases_url": "https://api.github.com/repos/example_org/example_repo/releases{/id}", - "deployments_url": "https://api.github.com/repos/example_org/example_repo/deployments", - "created_at": "2022-10-19T03:41:52Z", - "updated_at": "2022-10-23T23:12:34Z", - "pushed_at": "2023-01-20T09:03:04Z", - "git_url": "git://github.com/example_org/example_repo.git", - "ssh_url": "git@github.com:example_org/example_repo.git", - "clone_url": "https://github.com/example_org/example_repo.git", - "svn_url": "https://github.com/example_org/example_repo", - "homepage": "https://www.example.com", - "size": 642, - "stargazers_count": 1, - "watchers_count": 1, - "language": "TypeScript", - "has_issues": true, - "has_projects": false, - "has_downloads": true, - "has_wiki": false, - "has_pages": false, - "has_discussions": false, - "forks_count": 0, - "mirror_url": null, - "archived": false, - "disabled": false, - "open_issues_count": 4, - "license": { - "key": "mit", - "name": "MIT License", - "spdx_id": "MIT", - "url": "https://api.github.com/licenses/mit", - "node_id": "MDc6TGljZW5zZTEz" - }, - "allow_forking": false, - "is_template": false, - "web_commit_signoff_required": false, - "topics": [], - "visibility": "internal", - "forks": 0, - "open_issues": 4, - "watchers": 1, - "default_branch": "main", - "allow_squash_merge": true, - "allow_merge_commit": false, - "allow_rebase_merge": false, - "allow_auto_merge": true, - "delete_branch_on_merge": false, - "allow_update_branch": true, - "use_squash_pr_title_as_default": false, - "squash_merge_commit_message": "COMMIT_MESSAGES", - "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", - "merge_commit_message": "PR_TITLE", - "merge_commit_title": "MERGE_MESSAGE" - } - }, - "base": { - "label": "example_org:main", - "ref": "main", - "sha": "caf87bf0162986f2874ec1b668f1d576b9f99e76", - "user": { - "login": "example_org", - "id": 2345678, - "node_id": "MDEyOk9yZ2FuaXphdGlvbjIzNDU2Nzg=", - "avatar_url": "https://avatars.githubusercontent.com/u/2345678?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/example_org", - "html_url": "https://github.com/example_org", - "followers_url": "https://api.github.com/users/example_org/followers", - "following_url": "https://api.github.com/users/example_org/following{/other_user}", - "gists_url": "https://api.github.com/users/example_org/gists{/gist_id}", - "starred_url": "https://api.github.com/users/example_org/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/example_org/subscriptions", - "organizations_url": "https://api.github.com/users/example_org/orgs", - "repos_url": "https://api.github.com/users/example_org/repos", - "events_url": "https://api.github.com/users/example_org/events{/privacy}", - "received_events_url": "https://api.github.com/users/example_org/received_events", - "type": "Organization", - "site_admin": false - }, - "repo": { - "id": 553972582, - "node_id": "R_kgDOIQTzZg", - "name": "captain", - "full_name": "example_org/example_repo", - "private": true, - "owner": { - "login": "example_org", - "id": 2345678, - "node_id": "MDEyOk9yZ2FuaXphdGlvbjIzNDU2Nzg=", - "avatar_url": "https://avatars.githubusercontent.com/u/2345678?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/example_org", - "html_url": "https://github.com/example_org", - "followers_url": "https://api.github.com/users/example_org/followers", - "following_url": "https://api.github.com/users/example_org/following{/other_user}", - "gists_url": "https://api.github.com/users/example_org/gists{/gist_id}", - "starred_url": "https://api.github.com/users/example_org/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/example_org/subscriptions", - "organizations_url": "https://api.github.com/users/example_org/orgs", - "repos_url": "https://api.github.com/users/example_org/repos", - "events_url": "https://api.github.com/users/example_org/events{/privacy}", - "received_events_url": "https://api.github.com/users/example_org/received_events", - "type": "Organization", - "site_admin": false - }, - "html_url": "https://github.com/example_org/example_repo", - "description": null, - "fork": false, - "url": "https://api.github.com/repos/example_org/example_repo", - "forks_url": "https://api.github.com/repos/example_org/example_repo/forks", - "keys_url": "https://api.github.com/repos/example_org/example_repo/keys{/key_id}", - "collaborators_url": "https://api.github.com/repos/example_org/example_repo/collaborators{/collaborator}", - "teams_url": "https://api.github.com/repos/example_org/example_repo/teams", - "hooks_url": "https://api.github.com/repos/example_org/example_repo/hooks", - "issue_events_url": "https://api.github.com/repos/example_org/example_repo/issues/events{/number}", - "events_url": "https://api.github.com/repos/example_org/example_repo/events", - "assignees_url": "https://api.github.com/repos/example_org/example_repo/assignees{/user}", - "branches_url": "https://api.github.com/repos/example_org/example_repo/branches{/branch}", - "tags_url": "https://api.github.com/repos/example_org/example_repo/tags", - "blobs_url": "https://api.github.com/repos/example_org/example_repo/git/blobs{/sha}", - "git_tags_url": "https://api.github.com/repos/example_org/example_repo/git/tags{/sha}", - "git_refs_url": "https://api.github.com/repos/example_org/example_repo/git/refs{/sha}", - "trees_url": "https://api.github.com/repos/example_org/example_repo/git/trees{/sha}", - "statuses_url": "https://api.github.com/repos/example_org/example_repo/statuses/{sha}", - "languages_url": "https://api.github.com/repos/example_org/example_repo/languages", - "stargazers_url": "https://api.github.com/repos/example_org/example_repo/stargazers", - "contributors_url": "https://api.github.com/repos/example_org/example_repo/contributors", - "subscribers_url": "https://api.github.com/repos/example_org/example_repo/subscribers", - "subscription_url": "https://api.github.com/repos/example_org/example_repo/subscription", - "commits_url": "https://api.github.com/repos/example_org/example_repo/commits{/sha}", - "git_commits_url": "https://api.github.com/repos/example_org/example_repo/git/commits{/sha}", - "comments_url": "https://api.github.com/repos/example_org/example_repo/comments{/number}", - "issue_comment_url": "https://api.github.com/repos/example_org/example_repo/issues/comments{/number}", - "contents_url": "https://api.github.com/repos/example_org/example_repo/contents/{+path}", - "compare_url": "https://api.github.com/repos/example_org/example_repo/compare/{base}...{head}", - "merges_url": "https://api.github.com/repos/example_org/example_repo/merges", - "archive_url": "https://api.github.com/repos/example_org/example_repo/{archive_format}{/ref}", - "downloads_url": "https://api.github.com/repos/example_org/example_repo/downloads", - "issues_url": "https://api.github.com/repos/example_org/example_repo/issues{/number}", - "pulls_url": "https://api.github.com/repos/example_org/example_repo/pulls{/number}", - "milestones_url": "https://api.github.com/repos/example_org/example_repo/milestones{/number}", - "notifications_url": "https://api.github.com/repos/example_org/example_repo/notifications{?since,all,participating}", - "labels_url": "https://api.github.com/repos/example_org/example_repo/labels{/name}", - "releases_url": "https://api.github.com/repos/example_org/example_repo/releases{/id}", - "deployments_url": "https://api.github.com/repos/example_org/example_repo/deployments", - "created_at": "2022-10-19T03:41:52Z", - "updated_at": "2022-10-23T23:12:34Z", - "pushed_at": "2023-01-20T09:03:04Z", - "git_url": "git://github.com/example_org/example_repo.git", - "ssh_url": "git@github.com:example_org/example_repo.git", - "clone_url": "https://github.com/example_org/example_repo.git", - "svn_url": "https://github.com/example_org/example_repo", - "homepage": "https://www.example.com", - "size": 642, - "stargazers_count": 1, - "watchers_count": 1, - "language": "TypeScript", - "has_issues": true, - "has_projects": false, - "has_downloads": true, - "has_wiki": false, - "has_pages": false, - "has_discussions": false, - "forks_count": 0, - "mirror_url": null, - "archived": false, - "disabled": false, - "open_issues_count": 4, - "license": { - "key": "mit", - "name": "MIT License", - "spdx_id": "MIT", - "url": "https://api.github.com/licenses/mit", - "node_id": "MDc6TGljZW5zZTEz" - }, - "allow_forking": false, - "is_template": false, - "web_commit_signoff_required": false, - "topics": [], - "visibility": "internal", - "forks": 0, - "open_issues": 4, - "watchers": 1, - "default_branch": "main", - "allow_squash_merge": true, - "allow_merge_commit": false, - "allow_rebase_merge": false, - "allow_auto_merge": true, - "delete_branch_on_merge": false, - "allow_update_branch": true, - "use_squash_pr_title_as_default": false, - "squash_merge_commit_message": "COMMIT_MESSAGES", - "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", - "merge_commit_message": "PR_TITLE", - "merge_commit_title": "MERGE_MESSAGE" - } - }, - "_links": { - "self": { - "href": "https://api.github.com/repos/example_org/example_repo/pulls/15" - }, - "html": { - "href": "https://github.com/example_org/example_repo/pull/15" - }, - "issue": { - "href": "https://api.github.com/repos/example_org/example_repo/issues/15" - }, - "comments": { - "href": "https://api.github.com/repos/example_org/example_repo/issues/15/comments" - }, - "review_comments": { - "href": "https://api.github.com/repos/example_org/example_repo/pulls/15/comments" - }, - "review_comment": { - "href": "https://api.github.com/repos/example_org/example_repo/pulls/comments{/number}" - }, - "commits": { - "href": "https://api.github.com/repos/example_org/example_repo/pulls/15/commits" - }, - "statuses": { - "href": "https://api.github.com/repos/example_org/example_repo/statuses/07a6048532c799c58bf7eafdbc7d4eaf6b6bbde6" - } - }, - "author_association": "COLLABORATOR", - "auto_merge": null, - "active_lock_reason": null, - "merged": false, - "mergeable": null, - "rebaseable": null, - "mergeable_state": "unknown", - "merged_by": null, - "comments": 0, - "review_comments": 0, - "maintainer_can_modify": false, - "commits": 7, - "additions": 557, - "deletions": 45, - "changed_files": 27 - }, - "repository": { - "id": 553972582, - "node_id": "R_kgDOIQTzZg", - "name": "captain", - "full_name": "example_org/example_repo", - "private": true, - "owner": { - "login": "example_org", - "id": 2345678, - "node_id": "MDEyOk9yZ2FuaXphdGlvbjIzNDU2Nzg=", - "avatar_url": "https://avatars.githubusercontent.com/u/2345678?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/example_org", - "html_url": "https://github.com/example_org", - "followers_url": "https://api.github.com/users/example_org/followers", - "following_url": "https://api.github.com/users/example_org/following{/other_user}", - "gists_url": "https://api.github.com/users/example_org/gists{/gist_id}", - "starred_url": "https://api.github.com/users/example_org/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/example_org/subscriptions", - "organizations_url": "https://api.github.com/users/example_org/orgs", - "repos_url": "https://api.github.com/users/example_org/repos", - "events_url": "https://api.github.com/users/example_org/events{/privacy}", - "received_events_url": "https://api.github.com/users/example_org/received_events", - "type": "Organization", - "site_admin": false - }, - "html_url": "https://github.com/example_org/example_repo", - "description": null, - "fork": false, - "url": "https://api.github.com/repos/example_org/example_repo", - "forks_url": "https://api.github.com/repos/example_org/example_repo/forks", - "keys_url": "https://api.github.com/repos/example_org/example_repo/keys{/key_id}", - "collaborators_url": "https://api.github.com/repos/example_org/example_repo/collaborators{/collaborator}", - "teams_url": "https://api.github.com/repos/example_org/example_repo/teams", - "hooks_url": "https://api.github.com/repos/example_org/example_repo/hooks", - "issue_events_url": "https://api.github.com/repos/example_org/example_repo/issues/events{/number}", - "events_url": "https://api.github.com/repos/example_org/example_repo/events", - "assignees_url": "https://api.github.com/repos/example_org/example_repo/assignees{/user}", - "branches_url": "https://api.github.com/repos/example_org/example_repo/branches{/branch}", - "tags_url": "https://api.github.com/repos/example_org/example_repo/tags", - "blobs_url": "https://api.github.com/repos/example_org/example_repo/git/blobs{/sha}", - "git_tags_url": "https://api.github.com/repos/example_org/example_repo/git/tags{/sha}", - "git_refs_url": "https://api.github.com/repos/example_org/example_repo/git/refs{/sha}", - "trees_url": "https://api.github.com/repos/example_org/example_repo/git/trees{/sha}", - "statuses_url": "https://api.github.com/repos/example_org/example_repo/statuses/{sha}", - "languages_url": "https://api.github.com/repos/example_org/example_repo/languages", - "stargazers_url": "https://api.github.com/repos/example_org/example_repo/stargazers", - "contributors_url": "https://api.github.com/repos/example_org/example_repo/contributors", - "subscribers_url": "https://api.github.com/repos/example_org/example_repo/subscribers", - "subscription_url": "https://api.github.com/repos/example_org/example_repo/subscription", - "commits_url": "https://api.github.com/repos/example_org/example_repo/commits{/sha}", - "git_commits_url": "https://api.github.com/repos/example_org/example_repo/git/commits{/sha}", - "comments_url": "https://api.github.com/repos/example_org/example_repo/comments{/number}", - "issue_comment_url": "https://api.github.com/repos/example_org/example_repo/issues/comments{/number}", - "contents_url": "https://api.github.com/repos/example_org/example_repo/contents/{+path}", - "compare_url": "https://api.github.com/repos/example_org/example_repo/compare/{base}...{head}", - "merges_url": "https://api.github.com/repos/example_org/example_repo/merges", - "archive_url": "https://api.github.com/repos/example_org/example_repo/{archive_format}{/ref}", - "downloads_url": "https://api.github.com/repos/example_org/example_repo/downloads", - "issues_url": "https://api.github.com/repos/example_org/example_repo/issues{/number}", - "pulls_url": "https://api.github.com/repos/example_org/example_repo/pulls{/number}", - "milestones_url": "https://api.github.com/repos/example_org/example_repo/milestones{/number}", - "notifications_url": "https://api.github.com/repos/example_org/example_repo/notifications{?since,all,participating}", - "labels_url": "https://api.github.com/repos/example_org/example_repo/labels{/name}", - "releases_url": "https://api.github.com/repos/example_org/example_repo/releases{/id}", - "deployments_url": "https://api.github.com/repos/example_org/example_repo/deployments", - "created_at": "2022-10-19T03:41:52Z", - "updated_at": "2022-10-23T23:12:34Z", - "pushed_at": "2023-01-20T09:03:04Z", - "git_url": "git://github.com/example_org/example_repo.git", - "ssh_url": "git@github.com:example_org/example_repo.git", - "clone_url": "https://github.com/example_org/example_repo.git", - "svn_url": "https://github.com/example_org/example_repo", - "homepage": "https://www.example.com", - "size": 642, - "stargazers_count": 1, - "watchers_count": 1, - "language": "TypeScript", - "has_issues": true, - "has_projects": false, - "has_downloads": true, - "has_wiki": false, - "has_pages": false, - "has_discussions": false, - "forks_count": 0, - "mirror_url": null, - "archived": false, - "disabled": false, - "open_issues_count": 4, - "license": { - "key": "mit", - "name": "MIT License", - "spdx_id": "MIT", - "url": "https://api.github.com/licenses/mit", - "node_id": "MDc6TGljZW5zZTEz" - }, - "allow_forking": false, - "is_template": false, - "web_commit_signoff_required": false, - "topics": [], - "visibility": "internal", - "forks": 0, - "open_issues": 4, - "watchers": 1, - "default_branch": "main" - }, - "organization": { - "login": "example_org", - "id": 2345678, - "node_id": "MDEyOk9yZ2FuaXphdGlvbjIzNDU2Nzg=", - "url": "https://api.github.com/orgs/example_org", - "repos_url": "https://api.github.com/orgs/example_org/repos", - "events_url": "https://api.github.com/orgs/example_org/events", - "hooks_url": "https://api.github.com/orgs/example_org/hooks", - "issues_url": "https://api.github.com/orgs/example_org/issues", - "members_url": "https://api.github.com/orgs/example_org/members{/member}", - "public_members_url": "https://api.github.com/orgs/example_org/public_members{/member}", - "avatar_url": "https://avatars.githubusercontent.com/u/2345678?v=4", - "description": "Example organization description" - }, - "enterprise": { - "id": 12345, - "slug": "example-enterprise", - "name": "Example Enterprise", - "node_id": "E_kgDRKO7", - "avatar_url": "https://avatars.githubusercontent.com/b/12345?v=4", - "description": "Example enterprise description", - "website_url": "https://www.example.com", - "html_url": "https://github.com/enterprises/example-enterprise", - "created_at": "2022-09-19T23:04:52Z", - "updated_at": "2022-09-19T23:16:12Z" - }, - "sender": { - "login": "jdoe", - "id": 1234567, - "node_id": "MDQ6VXNlcjY3NTE3ODc=", - "avatar_url": "https://avatars.githubusercontent.com/u/1234567?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/jdoe", - "html_url": "https://github.com/jdoe", - "followers_url": "https://api.github.com/users/jdoe/followers", - "following_url": "https://api.github.com/users/jdoe/following{/other_user}", - "gists_url": "https://api.github.com/users/jdoe/gists{/gist_id}", - "starred_url": "https://api.github.com/users/jdoe/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/jdoe/subscriptions", - "organizations_url": "https://api.github.com/users/jdoe/orgs", - "repos_url": "https://api.github.com/users/jdoe/repos", - "events_url": "https://api.github.com/users/jdoe/events{/privacy}", - "received_events_url": "https://api.github.com/users/jdoe/received_events", - "type": "User", - "site_admin": false - } -} diff --git a/.thing/wt.md b/.thing/wt.md deleted file mode 100644 index a1504ebb..00000000 --- a/.thing/wt.md +++ /dev/null @@ -1,3 +0,0 @@ -# webhookthing - -[webhookthing](https://docs.webhookthing.com): Run webhooks locally with 1 click. diff --git a/.tokenami/tokenami.config.ts b/.tokenami/tokenami.config.ts deleted file mode 100644 index b78fad53..00000000 --- a/.tokenami/tokenami.config.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * TODO: See #33 and #90 of the Relivator's Roadmap - * @see https://github.com/tokenami/tokenami#readme - */ - -import { createConfig } from "@tokenami/dev"; - -export default createConfig({ - include: ["./src/**/*.{js,jsx,ts,tsx}"], - grid: "0.25rem", - responsive: { - medium: "@media (min-width: 1024px)", - "medium-self": "@container (min-width: 400px)", - }, - theme: { - alpha: {}, - anim: {}, - border: {}, - color: { - "slate-100": "#f1f5f9", - "slate-700": "#334155", - "sky-500": "#0ea5e9", - }, - ease: {}, - "font-size": {}, - leading: {}, - "line-style": {}, - radii: { - rounded: "10px", - circle: "9999px", - none: "none", - }, - size: {}, - shadow: {}, - surface: {}, - tracking: {}, - transition: {}, - weight: {}, - z: {}, - }, -}); diff --git a/.tokenami/tokenami.env.d.ts b/.tokenami/tokenami.env.d.ts deleted file mode 100644 index 2fcf1942..00000000 --- a/.tokenami/tokenami.env.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * TODO: See #33 and #90 of the Relivator's Roadmap - * @see https://github.com/tokenami/tokenami#readme - */ - -import { TokenamiProperties } from "@tokenami/dev"; - -import config from "./tokenami.config"; - -export type Config = typeof config; - -declare module "@tokenami/dev" { - // biome-ignore lint/suspicious/noEmptyInterface: <explanation> - interface TokenamiConfig extends Config {} -} - -declare module "react" { - // biome-ignore lint/suspicious/noEmptyInterface: <explanation> - interface CSSProperties extends TokenamiProperties {} -} diff --git a/.vscode/extensions.json b/.vscode/extensions.json index fad65727..ecf71aa2 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,25 +1,31 @@ { "recommendations": [ "aaron-bond.better-comments", - "streetsidesoftware.code-spell-checker", - "usernamehw.errorlens", + "adpyke.codesnap", + "astro-build.houston", + "biomejs.biome", + "bradlc.vscode-tailwindcss", + "charliermarsh.ruff", + "chunsen.bracket-select", + "davidanson.vscode-markdownlint", "dbaeumer.vscode-eslint", - "github.copilot", + "evondev.indent-rainbow-palettes", + "fabiospampinato.vscode-open-multiple-files", + "github.copilot-chat", + "github.github-vscode-theme", "lokalise.i18n-ally", - "ms-vscode.vscode-typescript-next", - "yzhang.markdown-all-in-one", - "davidanson.vscode-markdownlint", + "mattpocock.ts-error-translator", "mikekscholz.pop-icon-theme", - "esbenp.prettier-vscode", + "ms-python.python", + "mylesmurphy.prettify-ts", + "neptunedesign.vs-sequential-number", + "oderwat.indent-rainbow", + "streetsidesoftware.code-spell-checker", + "unifiedjs.vscode-mdx", + "usernamehw.errorlens", + "usernamehw.remove-empty-lines", "yoavbls.pretty-ts-errors", - "bradlc.vscode-tailwindcss", - "omkarbhede.tailwindcss-tune", - "zardoy.ts-essential-plugins", - "biomejs.biome", - "helixquar.randomeverything", - "dsznajder.es7-react-js-snippets", - "alduncanson.react-hooks-snippets", - "planbcoding.vscode-react-refactor", - "orta.vscode-twoslash-queries" + "yzhang.markdown-all-in-one", + "zardoy.ts-essential-plugins" ] } diff --git a/.vscode/launch.json b/.vscode/launch.json index 72e149d2..6acd8335 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,76 +1,74 @@ { - "version": "0.2.0", + "compounds": [ + { + "configurations": ["Launch Next.js"], + "name": "Launch Next.js" + }, + { + "configurations": ["Launch Next.js", "Launch Next.js in Chrome"], + "name": "Launch Next.js and Chrome" + } + ], "configurations": [ { "name": "Attach by Process ID", - "type": "node", - "request": "attach", "processId": "${command:PickProcess}", - "restart": true + "request": "attach", + "restart": true, + "type": "node" }, { + "console": "internalConsole", + "env": { + "NODE_OPTIONS": "--inspect" + }, "name": "Launch Next.js", - "type": "node", - "request": "launch", "program": "${workspaceFolder}/node_modules/next/dist/bin/next", - "env": { "NODE_OPTIONS": "--inspect" }, - "console": "internalConsole", + "request": "launch", "sourceMaps": true, - "trace": true + "trace": true, + "type": "node" }, { "name": "Launch Next.js in Chrome", - "type": "chrome", - "request": "launch", - "url": "http://localhost:3000", - "webRoot": "${workspaceFolder}" - }, - { - "name": "Launch Next.js in Edge", - "type": "msedge", "request": "launch", + "type": "chrome", "url": "http://localhost:3000", "webRoot": "${workspaceFolder}" }, { - "name": "Launch All Jest Tests", - "type": "node", - "request": "launch", + "args": ["--runInBand"], "envFile": "${workspaceFolder}/.env", + "name": "Launch All Jest Tests", "program": "${workspaceFolder}/node_modules/jest/bin/jest", - "args": ["--runInBand"] + "request": "launch", + "type": "node" }, { - "name": "Launch Current Jest Test", - "type": "node", - "request": "launch", - "program": "${workspaceFolder}/node_modules/jest/bin/jest", "args": ["${relativeFile}"], "console": "internalConsole", - "internalConsoleOptions": "openOnSessionStart" + "internalConsoleOptions": "openOnSessionStart", + "name": "Launch Current Jest Test", + "program": "${workspaceFolder}/node_modules/jest/bin/jest", + "request": "launch", + "type": "node" }, { + "console": "integratedTerminal", + "env": { + "NODE_OPTIONS": "--loader=tsx" + }, "name": "Launch Ava Test (experimental)", - "type": "node", + "outputCapture": "std", + "program": "${workspaceFolder}/node_modules/ava/entrypoints/cli.js", "request": "launch", - "program": "${workspaceFolder}/node_modules/ava/entrypoints/cli.mjs", + "runtimeArgs": ["${file}", "--break", "debug"], "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/ava", - "runtimeArgs": ["debug", "--break", "${file}"], - "skipFiles": ["<node_internals>/**/*.js"], - "outputCapture": "std", "runtimeVersion": "20.10.0", - "console": "integratedTerminal", - "env": { "NODE_OPTIONS": "--loader=tsx" } + "skipFiles": ["<node_internals>/**/*.js"], + "type": "node" } ], - "compounds": [ - { - "name": "Launch Next.js and Edge", - "configurations": ["Launch Next.js", "Launch Next.js in Edge"] - }, - { - "name": "Launch Next.js and Chrome", - "configurations": ["Launch Next.js", "Launch Next.js in Chrome"] - } - ] + "type": "commonjs", + "version": "0.2.0" } diff --git a/.vscode/settings.json b/.vscode/settings.json index 530fe4ee..326da7b1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,117 +1,463 @@ { - "typescript.tsdk": "node_modules/typescript/lib", - "typescript.enablePromptUseWorkspaceTsdk": true, - "tailwindCSS.experimental.classRegex": [ - ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], - ["cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], - ["tw=\"([^\"]*)\""] - ], - "i18n-ally.keystyle": "nested", - "css.validate": false, - "tailwindCSS.classAttributes": [ - "class", - "className", - "classNames", - "containerClassName" - ], - "i18n-ally.sourceLanguage": "en-us", - "i18n-ally.displayLanguage": "en-us", - "i18n-ally.localesPaths": ["src/data/i18n"], - "i18n-ally.enabledFrameworks": ["general", "react", "next-intl"], - "files.trimTrailingWhitespace": true, - "files.insertFinalNewline": true, - "files.associations": { - "*.ignore": "plaintext", - "*.txt": "plaintext" + "[appts.reliverse]": { + "vscodePreset": "default" }, - "editor.tabSize": 2, - "eslint.format.enable": true, - "[javascript]": { + "[css]": { + "editor.defaultFormatter": "vscode.css-language-features" + }, + "[html]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[javascript]": { + "editor.defaultFormatter": "biomejs.biome" + }, "[javascriptreact]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "biomejs.biome" }, - "[typescript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "[json]": { + "editor.defaultFormatter": "biomejs.biome" }, - "[typescriptreact]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "[json5]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[jsonc]": { + "editor.defaultFormatter": "biomejs.biome" }, "[markdown]": { "editor.defaultFormatter": "DavidAnson.vscode-markdownlint" }, - "markdownlint.config": { - "MD033": { - "allowed_elements": ["img", "p", "a", "details", "summary"] - } + "[python]": { + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.organizeImports": "explicit" + }, + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.formatOnSave": true }, - "tsEssentialPlugins.fixSuggestionsSorting": true, - "search.exclude": { - "**/node_modules": false, - "**/node_modulesDISABLE": true + "[typescript]": { + "editor.defaultFormatter": "biomejs.biome" }, - "tsEssentialPlugins.globalLibCompletions.action": "mark", - "tsEssentialPlugins.patchOutline": true, - "tsEssentialPlugins.outline.arraysTuplesNumberedItems": true, - "tsEssentialPlugins.renameImportNameOfFileRename": true, - "tsEssentialPlugins.signatureHelp.excludeBlockScope": true, - "tsEssentialPlugins.skipNodeModulesReferences": true, - "tsEssentialPlugins.suggestions.localityBonus": true, - "tsEssentialPlugins.tupleHelpSignature": true, - "tsEssentialPlugins.jsxEmmet.modernize": true, + "[typescriptreact]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[yaml]": { + "editor.defaultFormatter": "redhat.vscode-yaml" + }, + "biome.enabled": true, + "breadcrumbs.enabled": true, "cSpell.enabled": true, - "tsEssentialPlugins.arrayMethodsSnippets.enable": true, - "ava-runner.experimentalEnabled": false, + "cSpell.userWords": [ + "Menlo", + "Monaspace", + "astro", + "biomejs", + "dotjs", + "mjml", + "pwsh", + "quickfix", + "rgba", + "tson" + ], + "cSpell.words": ["callout", "combobox", "reliverse", "utapi"], + "chat.editor.fontFamily": "'JetBrains Mono', 'Geist Mono', 'Monaspace Argon Var', Menlo, Monaco, 'Courier New', monospace", + "css.lint.important": "ignore", + "css.lint.unknownAtRules": "ignore", + "debug.console.fontFamily": "'JetBrains Mono', 'Geist Mono', 'Monaspace Argon Var', Menlo, Monaco, 'Courier New', monospace", + "debug.toolBarLocation": "docked", + "diffEditor.experimental.showMoves": true, + "diffEditor.hideUnchangedRegions.enabled": true, + "editor.bracketPairColorization.enabled": true, + "editor.bracketPairColorization.independentColorPoolPerBracketType": true, + "editor.codeActionsOnSave": { + "quickfix.biome": "explicit", + "source.addMissingImports": "never", + "source.fixAll": "never", + "source.fixAll.eslint": "explicit", + "source.organizeImports": "never", + "source.organizeImports.biome": "never", + "source.removeUnused": "never" + }, + "editor.codeLensFontFamily": "'JetBrains Mono', 'Geist Mono', 'Monaspace Argon Var', Menlo, Monaco, 'Courier New', monospace", + "editor.colorDecorators": true, + "editor.cursorBlinking": "phase", + "editor.cursorSmoothCaretAnimation": "on", + "editor.cursorStyle": "line", + "editor.defaultFormatter": "biomejs.biome", + "editor.detectIndentation": false, + "editor.fontFamily": "'JetBrains Mono', 'Geist Mono', 'Monaspace Argon Var', Menlo, Monaco, 'Courier New', monospace", + "editor.fontLigatures": true, + "editor.fontSize": 14, + "editor.fontVariations": true, + "editor.fontWeight": "normal", + "editor.formatOnPaste": false, + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "file", + "editor.formatOnType": false, + "editor.guides.bracketPairs": true, + "editor.guides.indentation": false, + "editor.hideCursorInOverviewRuler": false, + "editor.inlayHints.fontFamily": "'JetBrains Mono', 'Geist Mono', 'Monaspace Argon Var', Menlo, Monaco, 'Courier New', monospace", + "editor.inlineSuggest.enabled": true, + "editor.inlineSuggest.showToolbar": "always", + "editor.insertSpaces": true, + "editor.linkedEditing": true, + "editor.minimap.autohide": false, + "editor.minimap.enabled": true, + "editor.minimap.renderCharacters": false, + "editor.multiCursorModifier": "alt", + "editor.rulers": [54, 74, 119], + "editor.smoothScrolling": true, + "editor.snippets.codeActions.enabled": true, + "editor.stickyScroll.enabled": true, + "editor.suggest.insertMode": "insert", + "editor.suggestSelection": "recentlyUsed", + "editor.tabSize": 2, + "editor.tokenColorCustomizations": { + "comments": "#746f68" + }, "editor.unicodeHighlight.allowedCharacters": { "a": true, - "а": true, + "І": true, "А": true, - "б": true, "В": true, - "е": true, "Е": true, - "з": true, "З": true, - "і": true, - "І": true, - "н": true, "Н": true, + "О": true, + "Р": true, + "С": true, + "Т": true, + "У": true, + "а": true, + "б": true, + "г": true, + "е": true, + "з": true, + "н": true, "о": true, "р": true, - "Р": true, "с": true, "у": true, - "У": true - }, - "jest.autoRevealOutput": "on-exec-error", - "cSpell.words": [ - "ecmascript", - "graphqlsp", - "nextjs", - "trpc", - "typecheck", - "webhookthing" - ], - "eslint.lintTask.options": "-c ${workspaceFolder}/eslint.config.ts", - "eslint.experimental.useFlatConfig": true, - "prettier.enable": true, - "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.fixAll.eslint": "explicit", - "source.organizeImports": "never" + "і": true, + "ا": true, + "ه": true, + "–": true }, - "eslint.validate": [ + "editor.unicodeHighlight.allowedLocales": { + "tr": true + }, + "editor.wordWrap": "on", + "eslint.codeActionsOnSave.mode": "problems", + "eslint.enable": true, + "eslint.format.enable": true, + "eslint.ignoreUntitled": true, + "eslint.lintTask.enable": true, + "eslint.lintTask.options": ".", + "eslint.probe": [ + "MDX", + "astro", + "github-actions-workflow", + "html", "javascript", "javascriptreact", + "json", + "json5", + "jsonc", + "markdown", + "toml", "typescript", "typescriptreact", - "vue", + "yaml" + ], + "eslint.rules.customizations": [ + { + "rule": "@stylistic/*", + "severity": "info" + }, + { + "rule": "perfectionist/*", + "severity": "info" + }, + { + "rule": "@typescript-eslint/consistent-type-imports", + "severity": "info" + }, + { + "rule": "import-x/newline-after-import", + "severity": "info" + }, + { + "rule": "readable-tailwind/*", + "severity": "info" + }, + { + "rule": "curly", + "severity": "info" + }, + { + "rule": "no-lonely-if", + "severity": "info" + }, + { + "rule": "@stylistic/linebreak-style", + "severity": "info" + }, + { + "rule": "jsonc/indent", + "severity": "info" + }, + { + "rule": "RULES-BELOW-ARE-NOT-AUTOFIXABLE", + "severity": "warn" + }, + { + "rule": "@typescript-eslint/no-unused-vars", + "severity": "warn" + }, + { + "rule": "@stylistic/no-tabs", + "severity": "error" + }, + { + "rule": "@stylistic/no-mixed-spaces-and-tabs", + "severity": "error" + }, + { + "rule": "@stylistic/no-mixed-operators", + "severity": "error" + }, + { + "rule": "@stylistic/max-statements-per-line", + "severity": "error" + }, + { + "rule": "@stylistic/max-len", + "severity": "error" + }, + { + "rule": "@stylistic/line-comment-position", + "severity": "error" + }, + { + "rule": "@stylistic/jsx-pascal-case", + "severity": "error" + }, + { + "rule": "@stylistic/jsx-child-element-spacing", + "severity": "error" + } + ], + "eslint.timeBudget.onFixes": { + "error": 25000, + "warn": 25000 + }, + "eslint.timeBudget.onValidation": { + "error": 25000, + "warn": 25000 + }, + "eslint.validate": [ + "MDX", + "astro", + "github-actions-workflow", "html", - "markdown", + "javascript", + "javascriptreact", "json", + "json5", "jsonc", - "yaml", - "toml" - ] + "markdown", + "toml", + "typescript", + "typescriptreact", + "yaml" + ], + "extensions.ignoreRecommendations": false, + "files.associations": { + "*.json": "json", + "*.mdx": "mdx", + "*.toml": "properties", + "*.txt": "plaintext", + ".env.example": "properties" + }, + "files.eol": "\n", + "files.insertFinalNewline": true, + "files.trimTrailingWhitespace": true, + "git.autofetch": true, + "git.confirmSync": false, + "git.enableSmartCommit": true, + "git.openRepositoryInParentFolders": "never", + "github.copilot.editor.enableAutoCompletions": true, + "html.format.indentInnerHtml": true, + "i18n-ally.displayLanguage": "en", + "i18n-ally.enabledFrameworks": ["general", "next-intl", "react"], + "i18n-ally.keysInUse": [ + "faq.1.details", + "faq.1.summary", + "faq.2.details", + "faq.2.summary", + "faq.3.details", + "faq.3.summary" + ], + "i18n-ally.keystyle": "nested", + "i18n-ally.localesPaths": ["messages"], + "i18n-ally.sourceLanguage": "en", + "indentRainbow.colors": [ + "rgba(58, 12, 163,0.15)", + "rgba(63, 55, 201,0.05)", + "rgba(63, 55, 201,0.025)", + "rgba(63, 55, 201,0.1)", + "rgba(63, 55, 201,0.15)", + "rgba(72, 12, 168,0.15)", + "rgba(86, 11, 173,0.15)", + "rgba(114, 9, 183, 0.15)", + "rgba(181, 23, 158,0.15)", + "rgba(247, 37, 133,0.15)" + ], + "javascript.format.semicolons": "insert", + "javascript.inlayHints.enumMemberValues.enabled": true, + "javascript.inlayHints.functionLikeReturnTypes.enabled": true, + "javascript.inlayHints.parameterNames.enabled": "all", + "javascript.inlayHints.parameterTypes.enabled": true, + "javascript.preferences.importModuleSpecifier": "non-relative", + "javascript.updateImportsOnFileMove.enabled": "always", + "json.validate.enable": false, + "markdown.extension.preview.autoShowPreviewToSide": false, + "markdown.preview.fontFamily": "'JetBrains Mono', 'Geist Mono', 'Monaspace Argon Var', Menlo, Monaco, 'Courier New', monospace", + "markdown.preview.scrollEditorWithPreview": true, + "markdown.preview.scrollPreviewWithEditor": true, + "markdownlint.config": { + "MD033": false, + "MD041": false + }, + "mdx.server.enable": true, + "notebook.output.fontFamily": "'JetBrains Mono', 'Geist Mono', 'Monaspace Argon Var', Menlo, Monaco, 'Courier New', monospace", + "openMultipleFiles.exclude": [ + "**/*.gif", + "**/*.jpeg", + "**/*.png", + "**/*.svg", + "**/*.txt", + "**/.git", + "**/.idea", + "**/.million", + "**/.next", + "**/.nyc_output", + "**/.pnp.*", + "**/.turbo", + "**/.venv", + "**/.yarn", + "**/build", + "**/coverage", + "**/dist", + "**/dist-dev", + "**/fixture", + "**/node_modules", + "**/package-lock.json", + "**/public", + "**/target", + "**/yarn.lock", + "**/yarn-error.log", + "addons/.output" + ], + "openMultipleFiles.ignore": [".gitignore"], + "openMultipleFiles.limit": 1000, + "outline.problems.badges": false, + "pop-icon-theme.files.associations": { + "*.astro": "astro", + "*.hbs": "handlebars", + "*.tson": "typescript", + "*.txt": "plaintext", + ".env.example": "properties", + "error.tsx": "console", + "layout.tsx": "mjml", + "loading.tsx": "dotjs", + "package.json": "json", + "page.tsx": "url" + }, + "pop-icon-theme.hidesExplorerArrows": true, + "prettify-ts.viewNestedTypes": true, + "problems.defaultViewMode": "table", + "problems.showCurrentInStatus": true, + "problems.sortOrder": "position", + "ruff.lineLength": 88, + "ruff.nativeServer": true, + "scm.inputFontFamily": "'JetBrains Mono', 'Geist Mono', 'Monaspace Argon Var', Menlo, Monaco, 'Courier New', monospace", + "tailwindCSS.classAttributes": ["class", "className", "classNames"], + "tailwindCSS.experimental.classRegex": [ + ["(?:'|\"|`)([^']*)(?:'|\"|`)", "cx\\(([^)]*)\\)"], + ["[\"'`]([^\"'`]*).*?[\"'`]", "cva\\(([^)]*)\\)"] + ], + "terminal.integrated.allowedLinkSchemes": [ + "C", + "file", + "http", + "https", + "mailto", + "vscode", + "vscode-insiders" + ], + "terminal.integrated.fontFamily": "'JetBrains Mono', 'Geist Mono', 'Monaspace Argon Var', Menlo, Monaco, 'Courier New', monospace", + "terminal.integrated.fontSize": 12, + "terminal.integrated.smoothScrolling": true, + "totalTypeScript.hideAllTips": true, + "totalTypeScript.hideBasicTips": true, + "tsEssentialPlugins.arrayMethodsSnippets.enable": true, + "tsEssentialPlugins.fixSuggestionsSorting": true, + "tsEssentialPlugins.globalLibCompletions.action": "mark", + "tsEssentialPlugins.jsxEmmet.modernize": true, + "tsEssentialPlugins.outline.arraysTuplesNumberedItems": true, + "tsEssentialPlugins.patchOutline": true, + "tsEssentialPlugins.renameImportNameOfFileRename": true, + "tsEssentialPlugins.signatureHelp.excludeBlockScope": true, + "tsEssentialPlugins.skipNodeModulesReferences": true, + "tsEssentialPlugins.suggestions.localityBonus": true, + "tsEssentialPlugins.tupleHelpSignature": true, + "typescript.enablePromptUseWorkspaceTsdk": true, + "typescript.format.semicolons": "insert", + "typescript.inlayHints.enumMemberValues.enabled": true, + "typescript.inlayHints.parameterNames.enabled": "all", + "typescript.preferences.autoImportFileExcludePatterns": [ + "next/dist/client/router.d.ts", + "next/router.d.ts" + ], + "typescript.preferences.importModuleSpecifier": "non-relative", + "typescript.preferences.includePackageJsonAutoImports": "on", + "typescript.referencesCodeLens.enabled": true, + "typescript.reportStyleChecksAsWarnings": true, + "typescript.updateImportsOnFileMove.enabled": "always", + "typescript.validate.enable": true, + "window.autoDetectColorScheme": true, + "window.commandCenter": true, + "window.title": "${dirty}${folderName}${separator}${activeEditorMedium}", + "workbench.colorCustomizations": { + "terminal.ansiBlack": "#21222c", + "terminal.ansiBlue": "#bd93f9", + "terminal.ansiBrightBlack": "#8d92ff", + "terminal.ansiBrightBlue": "#bd93f9", + "terminal.ansiBrightCyan": "#2cccff", + "terminal.ansiBrightGreen": "#20E3B2", + "terminal.ansiBrightMagenta": "#ff6bcb", + "terminal.ansiBrightRed": "#ff7979", + "terminal.ansiBrightWhite": "#ffffff", + "terminal.ansiBrightYellow": "#EAC394", + "terminal.ansiCyan": "#8be9fd", + "terminal.ansiGreen": "#20e3b2", + "terminal.ansiMagenta": "#ff6bcb", + "terminal.ansiRed": "#ff5555", + "terminal.ansiWhite": "#f8f8f2", + "terminal.ansiYellow": "#fde181", + "terminalCursor.background": "#868690", + "terminalCursor.foreground": "#43444D" + }, + "workbench.colorTheme": "Houston", + "workbench.editor.enablePreview": false, + "workbench.iconTheme": "pop-icon-theme", + "workbench.layoutControl.enabled": true, + "workbench.panel.defaultLocation": "bottom", + "workbench.preferredDarkColorTheme": "Default Dark Modern", + "workbench.preferredHighContrastColorTheme": "GitHub Dark High Contrast", + "workbench.preferredHighContrastLightColorTheme": "GitHub Light High Contrast", + "workbench.preferredLightColorTheme": "Default Light Modern", + "workbench.sideBar.location": "left", + "workbench.statusBar.visible": true } diff --git a/README.md b/README.md index d52ec7e2..22dfbba8 100644 --- a/README.md +++ b/README.md @@ -1,348 +1,693 @@ -# Relivator 1.2.5: The Most Feature-Rich Next.js 14 Starter +# Relivator 1.2.6: Next.js 15, React 19, TailwindCSS Template -<!-- https://github.com/blefnk/relivator#readme --> +<!-- For those who are viewing the current markdown file using: +- VSCode: Press F1 or Cmd/Ctrl+Shift+P and enter ">Markdown: Open Preview". It is recommended to install the "markdownlint" and "Markdown All in One" extensions. +- GitHub: Does this .md file appear different from what you are used to seeing on GitHub? Ensure the URL does not end with "?plain=1". --> -☀️ [Check Project Features](https://github.com/blefnk/relivator#project-roadmap-features-checklist) | 🌐 [Launch Relivator's Demo](https://relivator.bleverse.com) | 📖 [Read the Docs](https://docs.bleverse.com) +<div align="center"> -Stop running from one starter to the next. With Relivator, you'll have unlimited possibilities. You can create anything you want; all the tools are already prepared, just for you. +[🌐 Demo](https://relivator.bleverse.com) | [👋 Introduction](#introduction) | [🏗️ Installation](#installation) | [🩷 Sponsors](#sponsors) | [⚙️ Scripts](#scripts) | [🤔 FAQ](#faq) | [🔍 Details](#details) | [✅ Roadmap](#roadmap) | [📖 Changelog](#changelog) -We aim to create the world's most feature-rich and global Next.js starter. Offering more than just code—it's a journey. It's stable and ready for production. Scroll down and check out the breathtaking list of project features, including switching between Clerk/NextAuth.js and Drizzle's MySQL/PostgreSQL on the fly. +</div> -Please scroll down the page to see a lot of useful information about how everything works in the project, and a comprehensive list of project features as well. +<!-- 🚀 Yay! Thanks for the installation and welcome! If you like it, please consider giving us a star! ⭐ --> -## How to Install and Get Started +<!-- 👉 https://github.com/blefnk/relivator-nextjs-template 🙏 --> -1. **Essential Tools**: Ensure that [_VSCode_](https://code.visualstudio.com), [_Git_](https://learn.microsoft.com/en-us/devops/develop/git/install-and-set-up-git), _GitHub Desktop_ ([Windows/macOS](https://desktop.github.com) | [Linux](https://dev.to/rahedmir/is-github-desktop-available-for-gnu-linux-4a69)), _Node.js LTS_ ([Windows/macOS](https://nodejs.org) | [Linux](https://youtu.be/NS3aTgKztis)), and [_PNPM_](https://pnpm.io/installation) are installed. -2. **Project Cloning**: [_Create a new fork_](https://github.com/blefnk/relivator/fork) and use GitHub Desktop to download it. -3. **Configuration**: Open VSCode and load the project folder. Press `Ctrl+Shift+P` and search for `>Create New Terminal`. Enter `pnpm install` to install the packages. Next, copy the `.env.example` file to a new `.env` file and fill in at least the `NEXT_PUBLIC_DB_PROVIDER` and `DATABASE_URL` fields. Finally, send the database schema to your database using `pnpm mysql:push` or `pnpm pg:push`. -4. **Run, Stop, Build**: Use `pnpm dev` to run the app (visit <http://localhost:3000> to check it). Stop it by focusing on the console and pressing `Ctrl+C`. After making changes, build the app using `pnpm build`. _Thats okay if you see Clerk's warnings_ when executing `pnpm build`, this is a known issue not related to Relivator. -5. **Commit and Deploy**: Upload your project to your GitHub profile using GitHub Desktop. Then, deploy it by importing the project into [Vercel](https://vercel.com/new), making your website publicly accessible on the internet. If you wish to share your work, seek feedback, or ask for assistance, you're welcome to do so either [in our Discord server](https://discord.gg/Pb8uKbwpsJ) or [via GitHub discussions](https://github.com/blefnk/relivator/discussions). +Stop jumping from one starter to the next. With [Relivator](https://github.com/blefnk/relivator-nextjs-template#readme), your possibilities are endless! You can create anything you want; all the tools are ready and waiting for you. Please take a moment to read through the information below. You'll find helpful details about how everything works in the project, as well as an extensive list of features. -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fblefnk%2Frelivator&project-name=relivator&repository-name=relivator) +The entire Relivator project was developed by one person, [Nazar Kornienko (blefnk)](https://github.com/blefnk)! Some people have already contributed, and you’re welcome to do the same—any contributions at all are appreciated! Your contributions will not be forgotten; [our awesome community](https://discord.gg/Pb8uKbwpsJ) value them highly, and you might even receive financial gratitude from the project's creator in the future. Let's come together to create the most coolest Next.js template in the world! This will be a joint effort and a shared victory, a true win-win. Thank you all for your contributions and [financial support](#sponsors)! -Tip! You can create a folder, for instance, `home`, within the `src` directory, to store your project-specific files. It allows you for easy updates whenever Relivator has new versions. +<div align="center"> -> 🚀 **Ready to launch?** Start building your project with Relivator today! +<p> + <span> + <a href="https://github.com/blefnk/relivator-nextjs-template/blob/main/public/og.png"> + <picture> + <source media="(prefers-color-scheme: dark)" srcset="public/og.png" /> + <source media="(prefers-color-scheme: light)" srcset="public/og.png" /> + <img alt="Dark-themed image displaying various technologies and tools used in the Relivator project. The heading highlights Next.js 15, React 19, shadcn, and Tailwind Template. The image is divided into multiple sections listing technologies like shadcn, tailwind, next 15, react 19, clerk, authjs, drizzle, neon, ts 5.6, python, eslint 9, ts-eslint 8, knip, biome, unjs, and reliverse. The background features a grid layout with a minimalistic design, inspired by the Figma and Loading UI style." src="/public/og.png" width="35%" /> + </picture> + </a> + </span> +</p> -## Project Roadmap Features Checklist +[![Discord chat][badge-discord]][link-discord] +[![npm version][badge-npm]][link-npm] +[![MIT License](https://img.shields.io/github/license/blefnk/relivator-nextjs-template.svg?color=blue)](LICENSE) -**Note:** _Sometimes, we gift early access to Relivator's future plugins to three randomly selected individuals. We also give away various other interesting things. Simply `star this repository` and [let us know how to contact you](https://forms.gle/NXZ6QHpwrxh52VA36). For discussions, join [the project's Discord](https://discord.gg/Pb8uKbwpsJ)._ +[𝕏](https://x.com/blefnk) | [GitHub](https://github.com/blefnk) | [Slack](https://join.slack.com/t/reliverse/shared_invite/zt-2mq703yro-hKnLmsgbIQul0wX~gLxRPA) | [LinkedIn](https://linkedin.com/in/blefnk) | [Facebook](https://facebook.com/blefnk) | [Discord](https://discord.gg/Pb8uKbwpsJ) | [Fiverr](https://fiverr.com/blefnk) -_The roadmap below outlines the key features and improvements planned for implementation in this Next.js starter. `Items not marked may already be configured` but might not have undergone extensive testing. Should you find any mistakes, please create an issue._ +</div> -- [x] 1. Utilized [Next.js 14](https://nextjs.org) with **complete [Turbopack](https://turbo.build) support**, alongside [React 18](https://react.dev), [TailwindCSS](https://tailwindcss.com), and [TypeScript](https://typescriptlang.org) as the project's core technologies. -- [x] 2. Undertook [Drizzle ORM](https://orm.drizzle.team), utilizing **both MySQL and PostgreSQL** databases, and [PlanetScale](https://planetscale.com)/[Neon](https://neon.tech)/[Vercel](https://vercel.com)/[Railway](https://railway.app) services. -- [x] 3. Successfully configured `next.config.mjs` with i18n, MDX, and even [Million.js](https://million.dev) support. -- [x] 4. Strived for thorough documentation and a beginner-friendly approach throughout the project. -- [x] 5. Skillfully configured and commented on `middleware.ts` for i18n and next-auth. -- [x] 6. Set upped the Content-Security-Policy (CSP) system as a security measure to prevent XSS attacks (disabled by default). -- [x] 7. Provided exemplary VSCode settings and recommended extensions. -- [x] 8. Optimized the [Next.js Metadata API](https://nextjs.org/docs/app/building-your-application/optimizing/metadata) for SEO, integrating file-system handlers. -- [x] 9. Integrated a TailwindCSS screen size indicator for local project runs. -- [x] 10. Implemented extensive internationalization in 11 languages (English, German, Spanish, Persian, French, Hindi, Italian, Polish, Turkish, Ukrainian, Chinese) using the [next-intl](https://next-intl-docs.vercel.app) library, which works both on server and client, and included support for `next dev --turbo`. -- [x] 11. Implemented authentication through **both [Clerk](https://clerk.com) and [NextAuth.js](https://authjs.dev)**. -- [x] 12. Implemented [tRPC](https://trpc.io) and [TanStack Query](https://tanstack.com/query) (with [React Normy](https://github.com/klis87/normy#readme)) to have advanced server and client data fetching. -- [x] 13. Established a user subscription and checkout system using [Stripe](hhttps://github.com/stripe/stripe-node#readme). -- [x] 14. Ensured type-safety validations for project schemas and UI fields using [zod](https://zod.dev) library. -- [x] 15. Employed [EsLint](https://eslint.org) (with [Flat Config support](https://eslint.org/docs/latest/user-guide/configuring/configuration-files-new) and [@antfu/eslint-config](https://github.com/antfu/eslint-config#antfueslint-config), including a [TypeScript patch](https://github.com/antfu/eslint-ts-patch#eslint-ts-patch) to enable the `eslint.config.ts` filename), perfectly configured to work with both [Biome](https://biomejs.dev) and [Prettier](https://prettier.io) (including the latest Sort Imports addon) to ensure the code is readable, clean, and safe. **Tip:** use `pnpm ui:eslint` to open [ESLint Flat Config Viewer](https://github.com/antfu/eslint-flat-config-viewer#eslint-flat-config-viewer) UI tool. -- [x] 16. Elegantly executed the font system, utilizing [Inter](https://rsms.me/inter) and additional typefaces. -- [x] 17. Developed a storefront, incorporating product, category, and subcategory functionality. -- [x] 18. Designed a modern, cleanly composed UI using [Radix](https://radix-ui.com), with attractive UI components from [shadcn/ui](https://ui.shadcn.com). -- [x] 19. Composed a comprehensive, beginner-friendly `README.md`, including descriptions of [environment variables](https://nextjs.org/docs/basic-features/environment-variables). -- [x] 20. Blog functionality realized through the use of MDX files. -- [ ] 21. Use absolute paths everywhere where applied in the project. -- [ ] 22. Use [Kysely](https://kysely.dev) with Drizzle to achieve full TypeScript SQL query builder type-safety. -- [ ] 23. Translate README.md and related files into more languages. -- [ ] 24. Transform beyond a simple e-commerce store to become a universal website starter. -- [ ] 25. Tidy up `package.json` with correctly installed and orderly sorted packages in `dependencies` and `devDependencies`. -- [ ] 26. The project author should publish a series of detailed videos on how to use this project. There should also be some enthusiasts willing to publish their own videos about the project on their resources. -- [ ] 27. Reduce the number of project packages, config files, and etc., as much as possible. -- [ ] 28. Reduce HTML tag nesting and ensure correct usage of HTML tags whenever possible. -- [ ] 29. Prioritize accessibility throughout, for both app user UI (User Interface) and UX (User Experience), as well as developers' DX (Developer Experience). Maintain usability without compromising aesthetics. -- [ ] 30. Prefer using [const-arrow](https://freecodecamp.org/news/when-and-why-you-should-use-es6-arrow-functions-and-when-you-shouldnt-3d851d7f0b26) and [type](https://million.dev/docs/manual-mode/block) over [function](https://freecodecamp.org/news/the-difference-between-arrow-functions-and-normal-functions) and [interface](https://totaltypescript.com/type-vs-interface-which-should-you-use) where applicable, and vice versa where applicable correspondingly, with using helpful ESLint [arrow-functions](https://github.com/JamieMason/eslint-plugin-prefer-arrow-functions#readme) plugin, to maintain readable and clean code by adhering to specific [recommendations](https://youtu.be/nuML9SmdbJ4) for [functional programming](https://toptal.com/javascript/functional-programming-javascript). -- [ ] 31. Optimize all app elements to improve dev cold start and build speeds. -- [ ] 32. Move each related system to its special folder (into the `src/core` folder), so any system can be easily removed from the project as needed. -- [ ] 33. Move component styles to .css or .scss files, or use packages that provide "syntactic sugar" for styles in .tsx files by using [tokenami](https://github.com/tokenami/tokenami#readme) CSS library. Implement possibility to implement [Figma Tokens System](https://blog.devgenius.io/link-figma-and-react-using-figma-tokens-89e6cc874b4d) to work seamlessly with the project. Tip: go to point #90 of this roadmap to lean more about new ways to use CSS-in-JS. -- [ ] 34. Migrate to NextAuth.js' [next-auth@beta](https://npmjs.com/package/next-auth?activeTab=versions) ([discussions](https://github.com/nextauthjs/next-auth/releases/tag/next-auth%405.0.0-beta.4)), and to Clerk's [@clerk/*@alpha]. -- [ ] 35. Manage email verification, newsletter sign-ups, and email marketing via [Resend](https://resend.com) and [React Email](https://react.email). -- [ ] 36. Make sure each page and the middleware are green or yellow, but not red, upon build in the development terminal. -- [ ] 37. Make each environment variable optional, allowing the app to operate without anything configured, simply omitting specific code sections as necessary. -- [ ] 38. Keep the project on the best possible way of writing good and clean code, by following guidelines like [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript/tree/master/react) / [Airbnb React/JSX Style Guide](https://github.com/airbnb/javascript/tree/master/react). -- [ ] 39. Keep the project free from things like `@ts-expect-error`, `eslint-disable`, `biome-ignore`, and others related not very safety things. -- [ ] 40. Keep the cookie count as low as possible, prepare for a cookie-free future, implement cookie management and notifications. -- [ ] 41. Introduce a comment system for products, including Review and Question types. -- [ ] 42. Integrate valuable things from [Next.js' Examples](https://github.com/vercel/next.js/tree/canary/examples) into this project. -- [ ] 43. Integrate valuable insights from [Next.js Weekly](https://nextjsweekly.com/issues) into this starter. -- [ ] 44. Implement type-safe [GraphQL](https://hygraph.com/learn/graphql) support by using [Fuse.js](https://fusejs.org) framework. -- [ ] 45. Implement the best things from [Payload CMS](https://github.com/payloadcms/payload) with Relivator's improvements. -- [ ] 46. Implement Storybook 8.0 support (read the "[Storybook for React Server Components](https://storybook.js.org/blog/storybook-react-server-components)" announcement). -- [ ] 47. Implement smart and unified log system, both for development and production, both for console and writing to specific files. -- [ ] 48. Implement Sentry to handle errors and CSP reports for the application. -- [ ] 49. Implement Relivator's/Reliverse's own version of [Saas UI](https://saas-ui.dev) to be fully compatible with our project with only needed functionality, with using Tailwind and Shadcn instead of Chakra. -- [ ] 50. Implement our own fork of [Radix Themes](https://radix-ui.com) library with set up `<main>` as wrapper instead of its current `<section>`; OR implement our very own solution which generates Tailwind instead of Radix's classes. -- [ ] 51. Implement full [Million.js](https://million.dev) support (read [Million 3.0 Announcement](https://million.dev/blog/million-3) to learn more). -- [ ] 52. Implement file uploads using [UploadThing](https://uploadthing.com) and [Cloudinary](https://cloudinary.com). -- [ ] 53. Implement dynamic switching between app features, like database provider, by making corresponding checks for environment variables. -- [ ] 54. Implement docs to the project and move each explanation from the code into that docs. -- [ ] 55. Implement deep feature-parity and easy-migration compatibility with Reliverse. -- [ ] 56. Implement cooperation possibilities by using things like [liveblocks](https://liveblocks.io). -- [ ] 57. Implement CLI to quickly get Relivator with selected options only; try to use [Charm](https://charm.sh) things to build the Reliverse CLI. -- [ ] 58. Implement AI like GPT chat features by using [Vercel AI SDK](https://sdk.vercel.ai/docs) (see: [Introducing the Vercel AI SDK](https://vercel.com/blog/introducing-the-vercel-ai-sdk)). -- [ ] 59. Implement advanced theme switching without flashing, utilizing Tailwind Dark Mode with [React Server Side support](https://michaelangelo.io/blog/darkmode-rsc) and dynamic cookies. -- [ ] 60. Implement [Jest](https://jestjs.io) testing, optimized for Next.js. -- [ ] 61. Guarantee that every possible page is enveloped using predefined shell wrappers. -- [ ] 62. Generously comment throughout the code, while keeping it clean. -- [ ] 63. Fully develop advanced sign-up and sign-in pages, integrating both social media and classic form methods. -- [ ] 64. Follow the best practices from the articles and videos like "[10 React Antipatterns to Avoid](https://youtube.com/watch?v=b0IZo2Aho9Y)" (check theirs comment section as well). -- [ ] 65. Follow recommendations from [Material Design 3](https://m3.material.io) and other design systems when relevant. -- [ ] 66. Establish, document, and adhere to conventions, such as maintaining a single naming case style for files and variables. -- [ ] 67. Establish a comprehensive i18n, using country and locale codes, and support even more languages. Ensure native speakers verify each language following machine translation. Consider to use the [next-international](https://github.com/QuiiBz/next-international) library. -- [ ] 68. Ensure ultimate type-safety using strict mode in [TypeScript](https://typescriptlang.org) including ["Do's and Don'ts"](https://typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html) recommendations (without using [dangerous type assertions](https://youtube.com/watch?v=K9pMxqb5IAk), and with [optional types correct usage](https://youtube.com/watch?v=qy6IBZggXSQ), by also using `pnpm fix:ts` — once you run that, [TypeStat](https://github.com/JoshuaKGoldberg/TypeStat) will start auto-fixing TS typings); And also ensure type-safety with typedRoutes, zod, middleware, etc. -- [ ] 69. Ensure the project lacks any unused items, such as packages, libraries, and variables. Also, make sure the project's code adheres to the [Never Nester principles](https://youtube.com/watch?v=CFRhGnuXG-4). Because, as Linus Torvalds once said, _If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program_. -- [ ] 70. Ensure project has full support for [GSAP](https://gsap.com) (GreenSock Animation Platform) library, with convient ways to use @gsap/react [useGSAP() hook](https://gsap.com/docs/v3/React/tools/useGSAP). -- [ ] 71. Ensure full Next.js Edge support and compatibility. -- [ ] 72. Ensure full [Biome](https://biomejs.dev), [Bun](https://bun.sh), and [Docker](https://docker.com) support and compatibility. -- [ ] 73. Ensure all website languages are grammatically correct and adhere to the latest rules for each language. -- [ ] 74. Ensure all items in the project are sorted in ascending order unless different sorting is required elsewhere. -- [ ] 75. Ensure the project avoids using redundant imports, such as importing everything from React, when it's sufficient to import only the necessary hooks, for example. -- [ ] 76. Ensure accessibility for **users**, **developers** (both beginners and experts), **bots** (like [Googlebot](https://developers.google.com/search/docs/crawling-indexing/googlebot) or [PageSpeed Insights Crawler](https://pagespeed.web.dev)), for **everyone**. -- [ ] 77. Enhance `middleware.ts` configuration with multi-middleware implementation. -- [ ] 78. Employ all relevant [TanStack](https://tanstack.com) libraries. -- [ ] 79. Eliminate each disabling in the `.eslintrc.cjs` file, configure config to strict, but to be still beginner-friendly. -- [ ] 80. Elegantly configure `app.ts`, offering a single config to replace all possible others. -- [ ] 81. Develop workflows for both sellers and customers. -- [ ] 82. Develop an even more sophisticated implementation of user subscriptions and the checkout system via Stripe; and also write Jest/Ava tests for Stripe and use `.thing/hooks/stripe_*.json` [webhookthing](https://docs.webhookthing.com) data files for these tests. -- [ ] 83. Develop an advanced storefront featuring products, categories, and subcategories. -- [ ] 84. Develop an advanced 404 Not Found page with full internationalization support. -- [ ] 85. Develop advanced sign-up, sign-in, and restoration using email-password and magic links. -- [ ] 86. Decrease file count by merging similar items, etc. -- [ ] 87. Create the most beginner-friendly and aesthetically pleasing starter possible. -- [ ] 88. Create an advanced notification system, inclusive of toasters, pop-ups, and pages. -- [ ] 89. Create a new landing page with a distinctive design and update components, plus fully redesign all other pages and components. -- [ ] 90. Consider adding Facebook's [StyleX](https://stylexjs.com/blog/introducing-stylex). However, StyleX currently requires setting up Babel/Webpack in the project, which we avoid to maintain full Turbopack support. As a suitable alternative, consider jjenzz's [Tokenami](https://github.com/tokenami/tokenami#readme) or Chakra's [Panda CSS](https://panda-css.com). Possibly, we can make a choice between them all while bootstrapping the project with Reliverse CLI. These libraries help with avoiding the deprecated [initial idea](https://stylexjs.com/blog/introducing-stylex/#the-origins-of-stylex) for [CSS-in-JS](https://medium.com/dailyjs/what-is-actually-css-in-js-f2f529a2757). Learn more [here](https://github.com/reactwg/react-18/discussions/110) and in [Next.js docs](https://nextjs.org/docs/app/building-your-application/styling/css-in-js). -- [ ] 91. Confirm the project is free from duplicates, like files, components, etc. -- [ ] 92. Conduct useful tests, including possible stress tests, to simulate and assess app performance under high-traffic conditions. -- [ ] 93. Comprehensively configure Next.js 14 App Router, with API routes managed by Route Handlers, including the RSCs and all other new features. -- [ ] 94. Complete the BA11YC (Bleverse Accessibility Convention) checklist; which may relay on the following principle in the future: [DesignPrototype](https://uiprep.com/blog/ultimate-guide-to-prototyping-in-figma)-[CodePrototype](https://medium.com/@joomiguelcunha/the-power-of-prototyping-code-55f4ed485a30)-CodeTests-HqDesign-[TDD](https://en.wikipedia.org/wiki/Test-driven_development)-HqCode-[CI](https://en.wikipedia.org/wiki/CI/CD). -- [ ] 95. Complete parts of the [BA11YC (Bleverse Accessibility Convention) checklist](https://github.com/bs-oss/BA11YC). This includes using software [Design Patterns](https://refactoring.guru/design-patterns/what-is-pattern) for code refactoring. -- [ ] 96. Check all components with side-effects for re-rendering, it is recommended to re-render each component a maximum of 2 times ([good video about it (in Ukrainian)](https://youtu.be/uH9uMH2e5Ts)). -- [ ] 97. Boost app performance scores on platforms like Google PageSpeed Insights. Ensure the app passes all rigorous tests. -- [ ] 98. Apply the [next-usequerystate](https://github.com/47ng/next-usequerystate) library where appropriate ([read the article](https://francoisbest.com/posts/2023/storing-react-state-in-the-url-with-nextjs)). -- [ ] 99. All third-party libraries and React components should be appropriately isolated. This includes verifying data from these libraries, such as Clerk, and wrapping the components with the "use client" directive as necessary. -- [ ] 100. Add a reviews section to the landing page. Obtain feedback on Relivator from five widely recognized individuals on the web. -- [ ] 101. Add an admin dashboard that includes stores, products, orders, subscriptions, and payments. -- [ ] 102. Add global color variables to all places where they are applied, instead of having hardcoded colors. -- [ ] 103. Add pop-ups for cookies/GDPR notifications (with a respective management settings page), and Google floating notifications for quick login, etc. -- [ ] 104. Add some interesting and useful types to the project, for example, using the [type-fest](https://github.com/sindresorhus/type-fest) library. -- [ ] 105. Add the integration of a smart git-hooks system with various additional useful functionality. -- [ ] 106. Add the most valuable and useful ESLint things from [awesome-eslint](https://github.com/dustinspecker/awesome-eslint) collection. +> *«I couldn't find the ~~sports car~~ Next.js starter of my dreams, so I built it myself.»* © ~~Ferdinand Porsche~~ [@blefnk](https://github.com/blefnk) + +Our goal is to create the world's most feature-rich and globally accessible Next.js starter. It offers more than just code—it's an experience. It's stable and production-ready. Scroll down to see the impressive list of project features, including the ability to switch between Clerk/NextAuth.js and Drizzle's MySQL/PostgreSQL on the fly. Welcome to the Relivator starter and the Reliverse community! + +<p> + <span> + <a href="https://relivator.bleverse.com"> + <picture> + <source media="(prefers-color-scheme: dark)" srcset="public/internal/screenshot-dark.png" /> + <source media="(prefers-color-scheme: light)" srcset="public/internal/screenshot-light.png" /> + <img alt="Shows the landing page of Relivator Next.js template, with its logo and the phrase 'Relivator Empowers Your eCommerce with the Power of Next.js'." src="/public/internal/screenshot-light.png" width="53%" /> + </picture> + </a> + </span> + <span> + <a href="https://star-history.com/#blefnk/relivator-nextjs-template&Timeline"> + <picture> + <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=blefnk/relivator-nextjs-template&type=Timeline&theme=dark" /> + <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=blefnk/relivator-nextjs-template&type=Timeline" /> + <img alt="Chart which visualize the star history of Relivator repository." src="https://api.star-history.com/svg?repos=blefnk/relivator-nextjs-template&type=Timeline" width="45%" /> + </picture> + </a> + </span> +</p> + +## Introduction + +**✅ Relivator 1.2.6 uses the following dependencies (only some are listed)**: Next.js 15, React 19, TypeScript 5.5/5.6, Tailwind 3/4, tRPC 11, Clerk 5, Auth.js 5, ESLint 9 (with multiple plugins like typescript-eslint 8, react, unicorn, sonarjs, perfectionist, tailwindcss, readable-tailwind, import-x, jsx-a11y, security, markdown, mdx, json), Biome, Stripe, Million, Reliverse, next-intl, shadcn/ui, radix-ui, react-query, pnpm, zod, cn, turbo, Drizzle (Postgres, MySQL, SQLite, Neon, Railway, PlanetScale, Turso), GSAP, SWR, Resend, react-email, next-themes, Putout, Flowbite, Udecode, Slate, uploadthing, Radash, CSpell, TypeStat, Lucide & Radix Icons, Vercel & Loglib Analytics, Axios, Day.js, Embla Carousel, Execa, Math.js, UnJS libs (consola, fs-extra, pathe, etc), and much more + +**🎉 The upcoming Relivator 1.3.0 will have as few dependencies as possible.** Everything will work thanks to @reliverse/addons. Everything will be separated into its own libraries and will be published on npmjs and/or jsr. You will be able to install exactly what you need, including functionality and UI. You will have two options. One is to install the addons the classic way using 'package.json'. The other option is that all these addons can also be installed in a style inspired by shadcn/ui, where you keep all the content directly in your project (as it is currently in test mode in Relivator 1.2.6 (please check `addons` folder or run `pnpm addons`)), although the first option will be recommended for the most use cases. 'addons' folder already contains many cool things, especially related to codemods. It also includes the @reliverse/academy game, where you can check how good you know JavaScript/TypeScript, React/Next.js, Relivator/Reliverse, and even ESLint, ecosystems (you will even find there table of records and will can contest with other players if you share data/players.json and data/progress.json save files to them; the game has also achievement system). + +**🙏 Please help us reach 1500 stars on GitHub**: Once this project reaches this goal, I, @blefnk, the author of this project, will start my video course on the basics of web development (HTML, CSS, JS), React, Next.js, TypeScript, related libraries, and many other topics. This milestone will also affirm that Relivator and [Reliverse](https://github.com/blefnk/reliverse-website-builder) truly make sense to exist, leading to more frequent updates and further dedication to these projects. + +**⭐ Bookmark this page in your browser**: This project will only get better in the future. You can also click the star at the top of the page and add the repo to your collection to keep it easily accessible. + +## The Huge Relivator 1.2.6 is Finally Available + +[**👉 Read the Detailed Blog Post About This Update 👈**](https://docs.bleverse.com/en/blog/relivator/v126) + +Relivator 1.2.6 was released on August 4, 2024! We are now actively working on the next major update, Relivator 1.3.0, with the goal of making the project production-ready, clean, and high-quality. Please join us in actively searching for issues, contributing freely, and earning cool rewards. A canary branch will be launched soon, and the dev branch is already available for a limited time to all sponsors at any paid pledge level. + +*The fastest released version will be called: Relivator v1.3.0-canary.0*. + +**🔥 [Important note]: Please note that currently, v1.2.6 requires specifying Clerk environment variables keys, as its API has changed. We are working on making Clerk optional again. However, all other environment variables are optional. If this statement is incorrect, meaning something is broken somewhere, please let us know.** + +## What About the Future?! Any News on 1.3.0? + +**🎉 The upcoming Relivator 1.3.0 will have as few dependencies as possible! Yeah, finally!** + +I'm ([blefnk](https://github.com/blefnk)) working to automate the Relivator's developer experience as much as possible, including the installation process. The upcoming version 1.3.0 will feature a significant automated installation. If you wish to try the initial alpha version of one of my many automation scripts, use the `pnpm deps:install` (or `pnpm deps:install-all`) command. This script already allows you to install and remove project packages, and it also works as a linter. You can check the comprehensive number of predefined commands configured inside the 'scripts' section of the 'package.json' file. However, before running this script, you should manually install the essentials: + +- npx nypm add typescript tsx nypm @mnrendra/read-package @clack/prompts +- npx nypm add pathe fast-npm-meta semver @types/semver redrun axios +- bun|yarn|pnpm dlx jsr add @reliverse/core (or: npx jsr add @reliverse/core) + +Thanks to @reliverse/addons, everything will now work seamlessly with minimal dependencies. Each specific functionality and component is separated into its own library and published on [npmjs](https://npmjs.com) and/or [jsr](https://jsr.io), allowing you to install only what you need. With Relivator 1.3.0, you can say goodbye to the frustration of unnecessary components in templates. You get the core package and can add functionality and UI components as needed. + +The 'addons' folder is divided into two contexts: the browser and the terminal. The 'terminal' folder contains functions used by the CLI, while the 'src' folder is dedicated exclusively to the browser context, as the browser doesn't support certain features. So, the 'terminal' supports everything what contains 'browser', but not vice versa, unfortunately. + +You’ll have two installation options: the classic method using 'package.json' or a new approach inspired by shadcn/ui, where you keep all the content directly in your project (currently in test mode in Relivator 1.2.6—check the `addons` folder or run `pnpm addons`). While the classic method is recommended for most cases, feel free to explore the new approach! + +The 'addons' folder is already packed with many exciting features, especially related to codemods, and includes the **@reliverse/academy game**. This game allows you to test your knowledge of JavaScript/TypeScript, React/Next.js, Relivator/Reliverse (make food/tea/coffee before trying this test—it has a lot of questions!), and even ESLint v9 ecosystems. It features a leaderboard, enabling you to compete with others by sharing `data/players.json` and `data/progress.json` save files. Plus, an achievement system keeps you motivated! + +I can’t wait for you to experience the new and improved Relivator 1.3.0! By the way, the items in the [✅ Roadmap](#roadmap) section will finally be checked off! But to make 1.3.0 truly stable and great, let's first work together on Relivator v1.3.0-canary.0, which is coming soon! If you want to get it even faster, there is now a 'dev' branch. We recently opened the project pages on financial support platforms, and currently, any contribution grants you access to the 'dev' branch. Thank you for your attention! + +## Sponsors + +**[We're Growing Fast! A Huge Thanks to All Our Supporters!](https://github.com/blefnk/relivator-nextjs-template/stargazers)** + +Developing something as ambitious as Relivator obviously takes a lot of time, especially since the project is primarily developed by just one person. The development could be significantly accelerated by hiring additional developers. Therefore, @blefnk Nazar Kornienko, the author of this project, will be immensely grateful to anyone who can donate to the project in any amount. A big thank you to everyone in advance! + +*The Relivator is currently sponsored by the following awesome people/organizations:* + +### 💚 [GitHub Sponsors](https://github.com/sponsors/blefnk) 🩵 [PayPal](https://paypal.me/blefony) 🧡 [Patreon](https://patreon.com/blefnk) 💛 [Buy Me a Coffee](https://buymeacoffee.com/blefnk) 🩷 [Ko-fi](https://ko-fi.com/blefnk) + +*Love using this project? If you find this project useful, I'd appreciate a cup of coffee. You'll get Reliverse Pro, access to some private repositories, pre-release downloads, and the ability to influence my project planning. Please click on the donation platforms above to learn more. Thank you, everyone, for any kind of support!* + +- [mfpiano](https://youtube.com/@mfpiano) +- @svict4 + +### 💜 [Discord Server Boost](https://discord.gg/C4Z46fHKQ8) + +- @demiroo +- @Saif-V + +## 🖥️ Hire Me + +Hello, I'm [Nazar Kornienko](https://github.com/blefnk), a flexible web developer specializing in JavaScript/TypeScript and React/Next.js front-end development. If you’re looking for someone with my skill set, please contact me at <blefnk@gmail.com> or via [Discord](https://discordapp.com/users/611578864958832641). + +Please explore the current repository to learn more about my experience with the JavaScript/TypeScript and React/Next.js ecosystems. I'm available for both remote work and full-time positions. While my primary focus is frontend and CLI development, if you need help with code automation tools, small tasks related to Python or backend, or even designs in Figma, graphic design, or copywriting/marketing, feel free to reach out to me as well—I’d love to see how I can assist. + +## 🤝 Partnerships + +Starting with version 1.2.6, Relivator is now truly production-ready. However, there's still much to be accomplished, and you might be surprised when you check the [✅ Roadmap](#roadmap) section! To reach our goals, we are seeking partners to collaborate and support each other's projects' growth. If you're interested and would like to learn more, please feel free to email me at <blefnk@gmail.com>. + +### Our Partners + +<a href="https://railway.app?referralCode=sATgpf"> + <picture> + <source media="(prefers-color-scheme: dark)" srcset="public/partners-dark.svg" /> + <source media="(prefers-color-scheme: light)" srcset="public/partners-light.svg" /> + <img alt="Gallery of all partners' logos" src="/public/partners-dark.svg" width="64" /> + </picture> + <p>Railway</p> +</a> + +## 🙏 Dear Audience, I Need Your Help + +Currently, I’m in a challenging financial situation, so I would greatly appreciate any recommendations or mentions of my work and Relivator as part of my portfolio, such as on the pages of your own repositories. Your support would mean a lot to me! As a token of my appreciation, I’d be happy to send some interesting gifts to you. Thank you for your time and consideration! + +## Installation + +<!-- [![Bootstrap Relivator with a stack of your choice using the Reliverse CLI](https://github.com/blefnk/reliverse-website-builder)] --> + +**How to Install and Get Started:** You have two options for installation. You can either immediately deploy to Vercel using the button below and start working on the generated repository right away (but still read the information below), or you can follow the short or detailed manual installation instructions provided. + +**By The Way:** *Sometimes, we gift [`Reliverse Pro: Backers` pledge](https://patreon.com/blefnk), which gives you early access to the Reliverse projects ecosystem, including Relivator, as well as upcoming plugins, to randomly selected individuals. We also give away other interesting things. Simply `star this repository` and [let us know how to reach you](https://forms.gle/NXZ6QHpwrxh52VA36). To join the discussion, hop into [the project's Discord](https://discord.gg/Pb8uKbwpsJ).* + +### One-click Installation + +**🔥 [Important note]: Please note that currently, v1.2.6 requires specifying Clerk environment variables keys, as its API has changed. We are working on making Clerk optional again. However, all other environment variables are optional. If this statement is incorrect, meaning something is broken somewhere, please let us know.** + +By using this method, you will get only the front-end, with all the functionality disabled (learn how to enable it by reading the manual instructions below): + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fblefnk%2Frelivator-nextjs-template\&project-name=relivator\&repository-name=relivator) + +**Please note, as of version 1.2.6, it is better to use Clerk as the authProvider (specified in the `reliverse.config.ts` file) since this version has been more thoroughly tested with Clerk. We are working on fixing and improving the stability of NextAuth.js as an authentication provider.** + +### Manual Installation: Short Version (for speedrunners) + +1. **Tools**: Node.js LTS ([Windows/macOS](https://nodejs.org) | [Linux](https://youtu.be/NS3aTgKztis)) ➞ `corepack enable pnpm` ➞ [*VSCode*](https://code.visualstudio.com) ➞ [*Git*](https://learn.microsoft.com/en-us/devops/develop/git/install-and-set-up-git) ➞ *GitHub Desktop* ([Windows/macOS](https://desktop.github.com) | [Linux](https://dev.to/rahedmir/is-github-desktop-available-for-gnu-linux-4a69)) ➞ [Stripe CLI](https://docs.stripe.com/stripe-cli). Windows only: [PowerShell 7.4+](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.4#installing-the-msi-package). +2. **[Fork the repo](https://github.com/blefnk/relivator-nextjs-template/fork)**: Download your fork using GitHub Desktop. +3. **Setup**: `pnpm install && install:global && pnpm reli:setup` ➞ `pnpm reli:vscode` ➞ `cp .env.example .env` ➞ fill in the values inside of `.env` ➞ `pnpm db:push` ➞ `reliverse.config.ts`. +4. **Run, Build, Deploy**: Use `pnpm dev` to run the app. Stop with `Ctrl+C`. Build with `pnpm build`. Run `pnpm appts` to check the code. Upload to GitHub with GitHub Desktop. Deploy on [Vercel](https://vercel.com/new). + +### Manual Installation: Detailed Version (recommended) + +**🔥 [Important note]: Please note that currently, v1.2.6 requires specifying Clerk environment variables keys, as its API has changed. We are working on making Clerk optional again. However, all other environment variables are optional. If this statement is incorrect, meaning something is broken somewhere, please let us know.** + +▲ Hotline: [Email](mailto:blefnk@gmail.com) | [Discord](https://discord.gg/Pb8uKbwpsJ) | [Slack](https://join.slack.com/t/reliverse/shared_invite/zt-2mq703yro-hKnLmsgbIQul0wX~gLxRPA) | [Cal.com](https://cal.com/blefnk/reliverse) + +> I'm ([blefnk](https://github.com/blefnk)) working to automate the Relivator's installation process as much as possible. The upcoming version 1.3.0 will feature a significant automated installation. If you wish to try the alpha version of one of my many automation scripts, use the `pnpm deps:install` (or `pnpm deps:install-all`) command. However, before running this script, you should manually install the essentials (edit 'pnpm dlx jsr' if needed): `npx nypm add typescript tsx @clack/prompts @mnrendra/read-package nypm ora pathe fast-npm-meta semver @types/semver redrun && pnpm dlx jsr add @reliverse/core`. + +**Please note, as of version 1.2.6, it is better to use Clerk as the authProvider (specified in the `reliverse.config.ts` file) since this version has been more thoroughly tested with Clerk. We are working on fixing and improving the stability of NextAuth.js as an authentication provider.** + +1. **Essential Tools**: Ensure you have *Node.js LTS* ([Windows/macOS](https://nodejs.org) | [Linux](https://youtu.be/NS3aTgKztis)) installed. Then, run `corepack enable pnpm` to install [*pnpm*](https://pnpm.io/installation). Also, install [*VSCode*](https://code.visualstudio.com), [*Git*](https://learn.microsoft.com/en-us/devops/develop/git/install-and-set-up-git), *GitHub Desktop* ([Windows/macOS](https://desktop.github.com) | [Linux](https://dev.to/rahedmir/is-github-desktop-available-for-gnu-linux-4a69)), and [Stripe CLI](https://docs.stripe.com/stripe-cli). If you're a Windows user: install [PowerShell 7.4+](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.4#installing-the-msi-package) as well. +2. **Project Cloning**: [*Fork the repository*](https://github.com/blefnk/relivator-nextjs-template/fork) or click on the `Use this template` button. Use GitHub Desktop to download it to your device. The project size is about 12MB, but ensure you have at least 7GB of disk space for comfortable work, as the `node_modules` and `.next` folders require it. +3. **Configuration**: Open the project folder in VSCode. Install the recommended extensions from [.vscode/extensions.json](.vscode/extensions.json) and/or install the advanced VSCode configurations by using `pnpm reli:vscode` (choose the `default` preset for the best experience or the `ultimate` preset for the best experience). You can also refer to the [⚙️ Scripts](#scripts) and [🤔 FAQ](#faq) *RQ19* below to learn more about this script and its configurations. You can press `Cmd/Ctrl+F` and search for "`Q19`/`Q20`" if you want to install more extensions and settings (remember, more extensions mean slower VSCode). Then click `File > Exit` (VSCode will save all your open windows). Open VSCode again. Press `Ctrl+Shift+P` (or just `F1`) and search for `>Create New Terminal`, or just press **Cmd/Ctrl+Shift+~** *(on Windows make sure that it uses PowerShell 7.4+, click the arrow next to + ➞ Select Default Profile ➞ PowerShell)*. If VSCode prompts you to allow the usage of the project's TypeScript version, allow it if you're a Windows user. On other operating systems, you may or may not encounter path issues. +4. **Environment**: Run `pnpm install` (or `npx nypm install`) and/or, optionally, `install:global` to install the required packages. It is also recommended to configure `reliverse.config.ts` file. Then, optionally, you can use `pnpm deps:install-all`—especially `pnpm deps:install-all` (*this is currently an alpha script*)—to unlock some additional features, like the `eslint.config.ultimate.ts` preset (which will have a `.txt` extension by default starting with Relivator v1.3.0). (NOTE: As of Relivator v1.2.6, the `ultimate` preset is configured by default, so no action is required). Next, configure Relivator to meet your needs using the `pnpm reli:setup` and/or `pnpm reli:vscode` commands, and relaunch VSCode. You have two options: deploy with zero values in the `.env` file (resulting in just the frontend without features related to auth, database, pricing, etc.), or copy the `.env.example` file to a new `.env` file and fill in the values you want (everything is optional starting with Relivator v1.2.6). It is highly recommended to fill in the `DATABASE_URL` field. Then, set the database provider in `drizzle.config.ts` and make changes in related files if needed. Finally, send the database schema to the database using `pnpm db:push`. You can learn more about databases below in the current `README.md` file. +5. **Run, Stop, Build**: Run the app with `pnpm dev` or `pnpm turbo:dev` (interactive but unstable). Visit <http://localhost:3000> to check it out. Stop it by focusing on the console and pressing `Ctrl+C`. After making changes, build the app using `pnpm build` or `pnpm turbo:build`. *Don't worry if you see warnings related to Clerk, React Compiler, Babel, next-auth, etc. when running the build; these are known issues not related to Relivator.* Note that when using the pnpm [turbo:build](https://turbo.build) command, the VSCode terminal may not exit automatically. If this happens, press Cmd/Ctrl+C to close the process manually. +6. **Check, Commit, Deploy**: To check if the current codebase meets [@reliverse/standard](https://github.com/reliverse/standard), run `pnpm appts` (or `pnpm appts:noputout`, or `pnpm turbo:appts`, or `pnpm appts:nobuild`). Learn more about project scripts in the next section. If everything is fine, upload the project to your GitHub profile using GitHub Desktop. Finally, deploy it by importing the project into [Vercel](https://vercel.com/new), making the website publicly accessible on the internet. Alternatively, you can use `pnpm deploy` or just `vercel` to preview and inspect the local deployment without committing to GitHub every time. + +**It is recommended:** From time to time, run `pnpm reli:prepare`. This script executes `pnpm install`, which checks for issues or installs/removes manually added/removed dependencies in your `package.json` file. It also executes `pnpm latest`, which installs the latest versions of project dependencies. Finally, it runs `pnpm appts`, which will do its best to improve your code and check for any errors. **Note:** Since `pnpm latest` updates all packages to their latest versions, be aware that something in the code might break, especially if considerable time has passed since the last version of Relivator was released. Therefore, you can use, for example, the VSCode extension `Open Multiple Files` to easily find and fix broken code, or reach out to the [Relivator Discord server](https://discord.gg/Pb8uKbwpsJ) for assistance, or create a [GitHub Issue](https://github.com/blefnk/relivator-nextjs-template/issues). You can learn more about those scripts and the mentioned extension below in the current `README.md` file. + +**If you'd like** to share your work, get/provide feedback, or ask for help, feel free to do so either [in our Discord server](https://discord.gg/Pb8uKbwpsJ) or [via GitHub discussions](https://github.com/blefnk/relivator-nextjs-template/discussions). **Note:** Currently, the instructions above may be outdated. Please contact us if something goes wrong; everything will be updated in Relivator 1.3.0. + +## 🎶 Recommendation + +> Coding becomes a whole new experience with the right music, doesn't it? Enhance your workflow with Relivator and enjoy the soothing melodies from the [MF Piano YouTube channel](https://youtube.com/@mfpiano). This channel, run by my brother, offers beautiful piano covers that are perfect for background music. Your subscriptions, views, likes, and comments would mean the world to us and help his channel grow. Thank you for your support! + +## Scripts + +The project includes various scripts designed to enhance your developer experience. You can run any script using your terminal. Please note that some CLI scripts may require you to adjust the height of your terminal window to avoid UI anomalies. The Relivator allows you to use the following: + +1. **💪 Native Scripts**: These are commands configured in [package.json](package.json) and run by the package manager like [pnpm](https://pnpm.io), [bun](https://bun.sh), [yarn](https://yarnpkg.com), or [npm](https://nodejs.org/en/learn/getting-started/an-introduction-to-the-npm-package-manager). You can run these "native" scripts using commands like `pnpm [dev|build]` and `pnpm db:[push|studio]`. +2. **⚙️ Custom-Built Scripts**: These scripts are written in TypeScript and Python by Reliverse and the community and are mostly located in the `addons` folder. 🔥 *Please be cautious when using transformation scripts, as they are all in their initial versions. Ensure you commit your changes to your [version control provider](https://about.gitlab.com/topics/version-control) (such as [GitHub](https://github.com)) before using any of them.* They can be executed via the command line using `[appts|addons|reli|lint|fix]:*` or manually via `pnpm tsx path/to/file` or `py path/to/file` (e.g., `py addons/reliverse/relimter/python/tasks/math-to-mathjs.py`). +3. **🐍 Python Script Manager**: Can be executed using `reli:manager` or `py addons/reliverse/relimter/python/index.py`. Before running it, please read the [🐍 Python](#python) section below to learn how to prepare your workspace to run this manager. + +### package.json + +Below are some scripts configured in the `scripts` section of the `package.json` file (*the following text may be outdated in some places, please let us know if you find any inaccuracies*): + +- **`pnpm appts` / `pnpm appts:putout` / `pnpm appts:verbose` / `pnpm appts:nobuild`**: These commands perform a comprehensive codebase check. They sequentially run `pnpm knip` for various codebase checks, `pnpm format` to format code with Biome (or Prettier, coming soon), and `pnpm lint` for linting with Biome and ESLint (`pnpm lint:eslint`). **Linting may take some time, so please be patient.** The `pnpm appts:putout` command also runs `pnpm lint:putout`. Using `pnpm appts:verbose` displays detailed ESLint progress, useful if you suspect ESLint is stuck (it may be slow, not stuck). You can manually resolve issues by pressing `Ctrl+S` multiple times in VSCode until there are no issues in the "Problems" tab. Usually, issues are resolved by the second or third run. If some specific issues persist, it may mean they are not automatically fixed. Please try to fix them manually, contact us, or disable the rule. Many rules in `eslint.config.js` are disabled by default; enable only what you need. You may also want to run `pnpm reli:setup` to choose the RulesDisabled preset if you want to disable all "error" and "warning" rules at once. `pnpm appts` then runs `pnpm typecheck` for remaining TypeScript errors and `pnpm build`, but you may try `pnpm turbo:appts`, which runs `pnpm turbo:build` to speed up builds using Turborepo v2. Note that **`pnpm turbo:appts`** is a faster, interactive version of `appts` but may not work well with the VSCode terminal. Alternatively, you can use `pnpm appts:nobuild`, which performs only checking, linting, formatting, and fixing, without building. +- **`pnpm fix:putout-unstable`**: [Putout](https://github.com/coderaiser/putout) is a linter and formatter. While the formatter fixes issues reported by the linter, the `fix:putout-unstable` command also makes changes not flagged by the linter. Ensure you commit your code before running this command, as it might cause unexpected changes. After running it, review and adjust the changes in your codebase using VSCode Source Control (Cmd/Ctrl+Shift+G) or GitHub Desktop. +- **`pnpm db:[push|studio|generate|migrate]`**: `push` converts the TypeScript Drizzle schema to SQL and sends it to the DATABASE_URL; `studio` runs Drizzle Studio on <https://local.drizzle.studio>; `migrate` applies migrations generated by the `generate` command (you may not need this command anymore), based on the `drizzle.config.ts` file. +- **`pnpm stripe:listen`**: Runs the Stripe webhook listener and helps set up Stripe environment variables. The [Stripe CLI](https://docs.stripe.com/stripe-cli) must be installed for this command to work. +- **`pnpm addons`**: This command allows you to headlessly run some of the scripts located in the `addons` folder. Many scripts are still not added there, so please check the `addons` folder and run them manually using `pnpm tsx path/to/file` or `py path/to/file`. This also includes the game @reliverse/academy. In the future, it will have many different interesting features. Currently, it is a quiz with a leaderboard and achievements where you can test your knowledge of Relivator, JavaScript, TypeScript, React, and even ESLint. +- **`reli:manager`**: Learn more in the [🐍 Python](#python) section below. This is the alias for the `py addons/reliverse/relimter/python/index.py` command. +- **`pnpm latest`**: Updates all project packages to their latest stable versions, including some specific packages to their latest versions on rc/beta/alpha/next/canary branches. +- **`pnpm reli:vscode [nothing|minimal|default|ultimate]`**: Enhances VSCode settings with presets by Reliverse. This script adjusts your `settings.json`, `extensions.json`, and `launch.json` files. It will prompt for confirmation before overriding current files. Use `RQ20` to learn more about `.vscode` presets and font installation for the `ultimate` preset, or use the `default` preset (which doesn't contain custom fonts and themes). Choose `default`, `minimal`, or `nothing` if your PC or virtual machine is very slow. +- **`pnpm reli:help`**: Displays useful information about Relivator and Reliverse. +- **`pnpm lint:compiler`**: Runs the React Compiler health check. Note that React Compiler is disabled by default; see the FAQ section for more details. +- **`pnpm tw:[v4|v3]`**: Switches between [Tailwind CSS](https://tailwindcss.com/) v3 and the alpha v4 version. **Important**: After using this command, adjust comments in `postcss.config.cjs`. **Also**: When using Tailwind CSS v4, remove or comment out `tailwindPlugin` from `eslint.config.js` as it doesn't support v4. Some additional changes in the codebase may be required. +- **`pnpm reli:prepare`**: Learn more about this script in the previous section (Cmd/Ctrl+F "It is recommended"). + +```bash +# pnpm tsx reliverse.config.ts --details +ℹ ▲ Framework: Relivator v1.2.6 ▲ Engine: Reliverse v0.4.0 ▲ Hotline: https://discord.gg/Pb8uKbwpsJ +ℹ Relivator v1.2.6 Release Blog Post 👉 https://docs.bleverse.com/en/blog/relivator/v126 +ℹ Help Relivator become even better! Please star the repo – https://github.com/blefnk/relivator +ℹ For experienced users: run 'pnpm reli:prepare' to update all dependencies to the latest versions and check if the code requires any adjustments. +ℹ Meet quality standards: run 'pnpm appts' and 'pnpm fix:putout-unstable' to get linting, formatting, and more. +ℹ Unstable: try 'pnpm turbo:dev' and faster build with 'pnpm turbo:build': https://turbo.build/repo +ℹ The reactCompiler is enabled in next.config.js - it uses webpack now, so builds take longer. +ℹ Clerk: make sure to connect the domain in the Clerk dashboard so services like PageSpeed will work. +ℹ Please find Q21 in the FAQ of README.md. Copy the adapted bun scripts and replace the current ones in package.json - scripts for other package managers coming soon. +``` + +### Python + +👋 Hello, dear friend! Nice to see you here! I (@blefnk Nazar Kornienko) have a dream of making the open-source world better and of higher quality. I aspire to leave my mark in history by ensuring people genuinely enjoy programming and create quality products. I'm particularly passionate about clean code. The book "Clean Code" by Robert Martin is a must-have! + +That's why I've developed numerous tools in Relivator. Over the past few months leading up to Relivator 1.2.6, I've learned a lot. To avoid manually rewriting code, I've developed a unified script manager. The current version of the manager is still very unstable. You can visit the `addons/reliverse/relimter/python/index.py` file to learn more about how this script manager works. + +If you want to use this `Python Script Manager` (refer to [⚙️ Script](#scripts) to read the introduction), then please ensure your workspace is properly prepared for it. Please note that most scripts are largely untested. Commit your code before running any script. Increase your VSCode terminal window size to avoid UI glitches. Need help? Visit our [Discord](https://discord.gg/Pb8uKbwpsJ). Follow the steps below to get started (scroll down to learn even more Python commands): + +✅ After following the instructions above, you can safely run the script manager. + +1. Install [Python](https://python.org/downloads) and [Ruff](https://docs.astral.sh/ruff/installation). Install the following VSCode extensions: [Python](https://marketplace.visualstudio.com/items?itemName=ms-python.python) and [Ruff](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff). +2. Create a new `.venv` folder by pressing `Cmd/Ctrl+Shift+P` and running `>Python: Create Environment...` (VSCode will prompt you to choose the Python interpreter; choose the one installed in step 1). Then, choose `requirements.txt` as the package dependencies file. +3. Please note, VSCode's terminal automatically activates the environment after each launch. You can verify this by hovering over a [pwsh|bash|cmd] button in VSCode's terminal and looking for something like "`Python`: Activated environment for `.\.venv\Scripts\python.exe`". However, if you are using another IDE or an external terminal, you may need to activate the virtual environment manually: [Windows] `.venv/Scripts/activate`; [macOS/Linux] `source .venv/bin/activate`. +4. Ensure all requirement are installed correctly, just run `pip install -r requirements.txt`. + +🐍 Everything ready? Nice! Congratulations! Try running the `Python Script Manager` now by executing: `reli:manager` or `py addons/reliverse/relimter/python/index.py` + +### Useful Python Commands + +*We are still working on this section. The following description may currently be slightly incorrect.* + +```bash +# - Install the required dependencies: +pip install -r requirements.txt + +# - Make sure you have `pip-tools` installed to use `pip-compile` and `pip-sync`: +pip install pip-tools + +# - You can create/update a `requirements.txt` file: +pip freeze > requirements.txt + +# - Sync the dependencies: +pip-sync requirements.txt + +# - Remove a specific package (e.g., torch): +pip uninstall torch +pip freeze > requirements.txt +``` + +*Please note: If you have [UV](https://github.com/astral-sh/uv) installed and you've used a command like `uv pip install InquirerPy` and it shows an error like "The wheel is invalid", the easiest solution is to just use regular pip commands like `pip install InquirerPy`.* + +## FAQ + +**This is not only a Reliverse-specific FAQ but also a developers' FAQ for Next.js and the React ecosystem in general.** + +- **RQ1:** How do I enable the brand new React 19's React Compiler? **RA1:** Please visit the `next.config.js` file, and inside the `experimental` section, find `reactCompiler` and set it to `true`. Additionally, it's recommended to install `pnpm -D babel-plugin-react-compiler`. There are great ESLint rules, but they are uninstalled by default because they enable Babel/Webpack, which may slow down the build. If you just installed this plugin, then open `eslint.config.js`, find, and uncomment things related to it (use `Cmd/Ctrl+F` and search for `compiler`). + +- **RQ2:** How do I ensure my code is fully auto-fixed? **RA2:** Please note that you may need to press Cmd/Ctrl+S a few times to have the code fully fixed by various tools. + +- **RQ3:** How can I check the project's health? **RA3:** Run `pnpm appts` or `pnpm turbo:appts` (unstable but interactive and faster) to check the project's health. + +- **RQ4:** How can I update all packages to the latest version? **RA4:** For experienced developers, run `pnpm latest` to update all packages to the latest version. Alternatively, use 'pnpm reli:prepare' to update all dependencies and check if the code requires any adjustments. + +- **RQ5:** Why do I sometimes see the notification `Invalid JSON. JSON5: invalid character '#'`? **RA5:** No worries, looks like you've `thinker.sort-json` VSCode extension installed, and it seems to be an incorrect error thrown by this extension. But it's not recommended to use external sort-json-like extensions, because we've configured `eslint-plugin-jsonc`, which already does sorting in the more predicted way. If you still need `thinker.sort-json`, looks like it can't sort JSON files in rare random cases, but it works fine on the next file save (if your file doesn't have issues, of course). If this error is causing significant problems, such as preventing you from adding a word to CSpell, you can set `source.fixAll.sort-json` to `never` in `editor.codeActionsOnSave` of `.vscode/settings.json`. + +- **RQ6:** What should I do if I notice outdated information or other issues in the README.md or other files? **RA6:** In an effort to be as helpful as possible, this README contains a wealth of information. Some text may be outdated and will be updated as we grow. Please let us know on the [discussion page](https://github.com/blefnk/relivator-nextjs-template/discussions/6) if you notice any small issues like outdated info, broken links, or grammatical/spelling errors in README.md or other files. + +- **RQ6:** What versions of React and Next.js does the project currently use? **RA6:** The project currently uses `react@canary`, `react-dom@canary`, and `next@canary`. If React 19 and/or Next.js 15 have already been fully released, please remove them from the `latest` script and run, for example, `npx nypm add react@latest react-dom@latest next@latest` in the terminal. You can do the same with any other rc-alpha-beta-next-canary-dev-experimental-etc dependencies, on your choice, if their stable version has already been released. + +- **RQ7:** Where should I store project-specific files, and how do I handle ESLint issues? **RA7:** You can use the `src/cluster` folder to store project-specific files. This makes it easy to update Relivator to new versions. Learn more by visiting the Dashboard's main page on the development environment. After doing this, be prepared to see many issues pointed out by the ESLint config. Run `pnpm lint` to apply auto-fixes; **linting can take some time, so please be patient**. You may need to run `lint` or `lint:eslint` again if some issues are not fixed automatically. You can also open those files manually and press `Ctrl+S` multiple times until there are no issues in the VSCode "Problems" tab. Typically, by using the CLI, the issues will be resolved by the second or third run. Next, install the `Open Multiple Files` extension in VSCode; right-click on the `src/cluster` folder, select `Open Multiple Files`, and press Enter. Fix all issues. If you are proceeding incrementally, you can suppress certain ESLint/Biome rules (`// eslint-disable-next-line ...`, `// biome-ignore ...`, or completely disable the rule in the relevant config) or TypeScript errors (`@ts-expect-error`), though this is not recommended. + +- **RQ8:** Weird things happening when formatting code? The code looks one way, and then the next second, it looks different? For example, you see the number of code lines increasing and then decreasing at the same time upon file saving? Without changing the code, does Biome notify you e.g. "Fixed 6 files" instead of "No fixes needed" when you rerun `pnpm appts`? **RA8:** Congrats! You've encountered a conflict between linters or formatters. First, we recommend opening the `.vscode/settings.json` file, finding the `eslint.rules.customizations` section, and changing the `severity` from `"off"` to `"info"` (if `"off"` is present). Setting it to `"info"` will help you realize that one of the conflicting parties is potentially a rule in that `eslint.rules.customizations`. Next, you can try to correct files like `eslint.config.js` (e.g., disable that conflicting rule), `biome.json`, `.vscode/settings.json`, etc. You can also try to disable Biome or ESLint formatters completely, by setting `biome.enabled` or `eslint.enable` (or `eslint.format.enable`) to "false" in the `.vscode/settings.json` file. What about that "Fixed 6 files" example? It means Biome changed code in some files in the way which is different from ESLint. + +- **RQ9:** What should I do if I get a Babel warning about code generator deoptimization? **RA9:** This is a known issue and can be ignored. One of the reason occurs because the React Compiler is not yet fully natively supported by Next.js, it temporarily enables Babel to make the Compiler work. Also, don't worry if you see warnings thrown by Clerk, next-auth, or others when running `pnpm build` (mainly on Windows and Node.js); it's okay, this is a known issue not related to Relivator. It is also okay if pnpm tells you `Issues with peer dependencies found`; you can hide this warning by editing `pnpm.overrides` in the `package.json` file. **P.S.** Ignore the `Unexpected value or character.` error from Biome if you see it in the `globals.css` file. This is a false error, which you can hide by filtering `!globals.css` or just `!**.css` in the VSCode's Problems tab (use `!**.css, !**/node_modules/**` there if VSCode's Biome extension parses node_modules for some unknown reason). + +- **RQ10:** Can I open multiple files in my VSCode? **RA10:** We recommend the `Open Multiple Files` extension. Just right-click on the desired folder, e.g., `src`, and choose "Open Multiple Files". + +- **RQ11:** I have a strange `Each child in a list should have a unique "key" prop`. Any tips? **RA11:** If you see something like `at meta / at head` below this error, or `<locals>, but the module factory is not available. It might have been deleted in an HMR update.`, first try disabling `experimental.instrumentationHook`, if you have it, in `next.config.js`. You can also try deleting the `.next` folder. Please contact us if the problem persists. + +- **RQ12:** Million Lint reports `Attempted import error: '__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED' is not exported from 'react' (imported as 'L').` during the A12#12 What should I do? **RA12:** The easiest solution is to copy the path to the component file that appears under this error and add it to `filter.exclude` in the `next.config.js` file. Generally, the key word is `use`. Click on the component path below this error. It seems Million has not listed every possible external `useSomething`. Many `useSomething` hooks work fine, but some may cause issues. This error is also triggered if you use `"use client";` but no client-specific features are utilized. In this case, remove this directive. Additionally, Million.js sometimes doesn't recognize the `import * as React from "react";` statement – so you need to remove it and explicitly import each React hook, type, etc. This line was removed from over 80 files in Relivator v1.2.6 for this reason. + +- **RQ13:** Why do I see a warning in the terminal containing the message `terminating connection due to immediate shutdown command`? **RA13:** This warning occurs because providers like Neon disconnect users from `localhost` if they are inactive for about 5 minutes. Simply refresh the page to restore the connection. + +- **RQ14:** How do I remove unused keys from JSON files? **RA14:** Install the `i18n Ally` VSCode extension, open its tab, click on refresh in the `usage report`, right-click on the found unused keys, and select remove. + +- **RQ15:** How can I grant admin rights to myself or another user? **RA15:** Run `pnpm db:studio`, navigate to the `${databasePrefix}_user` table, and set `role: admin` for the desired user. In the future, if you have admin rights, you will be able to change user privileges directly from the frontend admin page. + +- **RQ16:** What does the `DEV_DEMO_NOTES` environment variable mean? **RA16:** Do not use it. It is only used on the official [Relivator demo website](https://relivator.bleverse.com) to showcase certain features that are not needed in real-world applications. + +- **RQ17:** I'm using PlanetScale as my database provider. After taking a break from the project, I'm now encountering an "unable to connect to branch" error. How can I fix this? **RA17:** Go to the PlanetScale dashboard and click on the `wake up` button. Please contact us if the database is not asleep and the problem persists. + +- **RQ18:** I have build/runtime errors indicating that Node.js utilities like `net`, `tls`, `perf_hooks`, and `fs` are not found. What should I do? **RA18:** Do not install these utilities; it won't fix the issue. Remember, never keep code in the `utils` folder that *can only run on the server*. Otherwise, you will encounter anomalies during the project build. For example, an error like `node:` and `file:` not found, or the package `fs`, `crypto`, etc. not found. Want to see the error for yourself? Move the file `src/server/api/uploadthing/react.ts` to `src/utils`, import it in this file, run `pnpm build`, get scared, remove the import, and move the file back to its place. You may find on the web the solutions suggesting to add configurations like `"node": { "net": "empty", "tls": "empty", "perf_hooks": "empty", "fs": "empty" }` or `"browser": { "net": false, "tls": false, "perf_hooks": false, "fs": false }` into `package.json` or to the webpack config, but these may not help you. **The main issue likely lies in the following:** You've triggered client-side code. For example, you might have a hook file in the `utils` folder with a corresponding `useEffect` React hook. To debug, try using the global search functionality in the IDE. Note that commenting out the lines may not be the quickest solution in this case, unlike in other debugging scenarios. + +- **RQ19:** I love all kinds of interesting things! Can you recommend any cool VSCode extensions? **RA19:** Of course! Just replace the current code in `.vscode/extensions.json` with the one from `addons/reliverse/presets/vscode/[default|minimal|ultimate]/extensions.json`. Remember, performance issues are possible, so you can just install what you want. Alternatively, you can just run the `pnpm reli:vscode` command to switch easily, and use `Cmd/Ctrl+Shift+P` ➞ `>Extensions: Show Recommended Extensions`. + + The best way to install this opinionated list of extensions, which are in the `ultimate` preset (although `default` is recommended by us), is to open the project folder in VSCode. Then, install them by using `Ctrl+Shift+P` (or just `F1`) and typing `>Extensions: Show Recommended Extensions`. Click on the cloud icon (`Install Workspace Recommended Extensions`). Wait for the completion. Click `File > Exit` (this will save all your open windows). Open VSCode again, and you are ready to go. The configuration for these extensions is already prepared for you. You can learn more about these extensions, which the `ultimate` preset contains, on the corresponding pages. + + *And, remember! If you have something broken, you always can find the default files content of `.vscode` folder in the `.vscode/presets/default` folder.* + + <details> + <summary>[Reveal the spoiler]</summary> + + This list may be outdated, and will be updated in Relivator v1.3.x. + + 1. [aaron-bond.better-comments](https://marketplace.visualstudio.com/items?itemName=aaron-bond.better-comments) + 2. [adpyke.codesnap](https://marketplace.visualstudio.com/items?itemName=adpyke.codesnap) + 3. [astro-build.houston](https://marketplace.visualstudio.com/items?itemName=astro-build.houston) + 4. [biomejs.biome](https://marketplace.visualstudio.com/items?itemName=biomejs.biome) + 5. [bradlc.vscode-tailwindcss](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) // tw v3 == release version | tw v4 == pre-release version + 6. [chunsen.bracket-select](https://marketplace.visualstudio.com/items?itemName=chunsen.bracket-select) + 7. [davidanson.vscode-markdownlint](https://marketplace.visualstudio.com/items?itemName=davidanson.vscode-markdownlint) + 8. [dbaeumer.vscode-eslint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + 9. [evondev.indent-rainbow-palettes](https://marketplace.visualstudio.com/items?itemName=evondev.indent-rainbow-palettes) + 10. [fabiospampinato.vscode-open-multiple-files](https://marketplace.visualstudio.com/items?itemName=fabiospampinato.vscode-open-multiple-files) + 11. [github.copilot-chat](https://marketplace.visualstudio.com/items?itemName=github.copilot-chat) + 12. [github.github-vscode-theme](https://marketplace.visualstudio.com/items?itemName=github.github-vscode-theme) + 13. [lokalise.i18n-ally](https://marketplace.visualstudio.com/items?itemName=lokalise.i18n-ally) + 14. [mattpocock.ts-error-translator](https://marketplace.visualstudio.com/items?itemName=mattpocock.ts-error-translator) + 15. [mikekscholz.pop-icon-theme](https://marketplace.visualstudio.com/items?itemName=mikekscholz.pop-icon-theme) + 16. [mylesmurphy.prettify-ts](https://marketplace.visualstudio.com/items?itemName=mylesmurphy.prettify-ts) + 17. [neptunedesign.vs-sequential-number](https://marketplace.visualstudio.com/items?itemName=neptunedesign.vs-sequential-number) + 18. [oderwat.indent-rainbow](https://marketplace.visualstudio.com/items?itemName=oderwat.indent-rainbow) + 19. [streetsidesoftware.code-spell-checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) + 20. [unifiedjs.vscode-mdx](https://marketplace.visualstudio.com/items?itemName=unifiedjs.vscode-mdx) + 21. [usernamehw.errorlens](https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens) + 22. [usernamehw.remove-empty-lines](https://marketplace.visualstudio.com/items?itemName=usernamehw.remove-empty-lines) + 23. [yoavbls.pretty-ts-errors](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors) + 24. [yzhang.markdown-all-in-one](https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one) + 25. [zardoy.ts-essential-plugins](https://marketplace.visualstudio.com/items?itemName=zardoy.ts-essential-plugins) + + **"TypeScript Essential Plugins" Extension Notes**: You can configure extension settings by opening VSCode Settings UI and searching for `@ext:zardoy.ts-essential-plugins` there. The quote from [VSCode Extension Repository](https://github.com/zardoy/typescript-vscode-plugins#readme): «Feature-complete TypeScript plugin that improves every single builtin feature such as completions, definitions, references and so on, and also adds even new TypeScript killer features, so you can work with large codebases faster! We make completions more informative. Definitions, references (and sometimes even completions) less noisy. And finally our main goal is to provide most customizable TypeScript experience for IDE features.» + + </details> + +- **RQ20:** *[Related to the previous question]* How can I improve my visual experience with VSCode? **RA20:** The project already has a well-configured `.vscode/settings.json`, but we recommend using our very opinionated configs presets. You have choice to install `default` or `ultimate` (`default` is recommended). **To activate the preset run `pnpm reli:vscode`.** For `ultimate` preset don't forget to install the required stuff: the static, means not variable, versions of [JetBrains Mono](https://jetbrains.com/lp/mono) (recommended) and/or [Geist Mono](https://vercel.com/font) and/or [Monaspace](https://monaspace.githubnext.com) (small manual configuration not or may be needed if you don't want to use `JetBrains Mono` on `ultimate` preset). Next, for `ultimate`, install the recommended `pop-icon-theme` VSCode icons pack extension. Finally, make sure to install the extensions from `Q19`, especially, install the recommended by us `GitHub Light` and `Houston` (by Astro developers) themes. Please note that after installing the Houston theme, you will find a new corresponding tab on the sidebar (🧑‍🚀 there is your new friend, which reacts while you've code issues!), you can of course remove this tab by right-clicking, but we recommend simply dragging this panel to the bottom of the Folders tab. + + - TODO: Fix 'Geist Mono' and 'Monaspace Argon Var', which looks like use Medium/Bold variation instead of Regular (`"editor.fontWeight": "normal"` doesn't help here). 'JetBrains Mono' works fine.* + - TODO: Do we really need to duplicate fonts for every single thing?* 🤔 + +<!-- + - **RQ??:** [Related to the previous question] Why did you switch the behavior of the `Cmd/Ctrl` and `alt/opt` keys? + **RA??:** Please note that you may need to press Cmd/Ctrl+S a few times to have the code fully fixed by various tools. +--> + +<!-- + - **RQ??:** [Relivator 1.3.0] How can I improve the experience with the CSpell (Code Spell Checker) extension? + **RA??:** Install the [CSpell VSCode extension](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker), install the CSpell npm package as a dev dependency if it's not installed (check your `package.json` file), install the necessary packages using your package manager (by using `npx nypm add -D @cspell/dict-companies @cspell/dict-de-de @cspell/dict-es-es @cspell/dict-fr-fr @cspell/dict-fullstack @cspell/dict-it-it @cspell/dict-markdown @cspell/dict-npm @cspell/dict-pl_pl @cspell/dict-tr-tr @cspell/dict-typescript @cspell/dict-fa-ir @cspell/dict-uk-ua cspell`), and add these lines to the `cspell.json` file: + + ```json + { + "import": [ + "@cspell/dict-typescript/cspell-ext.json", + "@cspell/dict-companies/cspell-ext.json", + "@cspell/dict-fullstack/cspell-ext.json", + "@cspell/dict-markdown/cspell-ext.json", + "@cspell/dict-npm/cspell-ext.json", + "@cspell/dict-de-de/cspell-ext.json", + "@cspell/dict-es-es/cspell-ext.json", + "@cspell/dict-fa-ir/cspell-ext.json", + "@cspell/dict-fr-fr/cspell-ext.json", + "@cspell/dict-it-it/cspell-ext.json", + "@cspell/dict-pl_pl/cspell-ext.json", + "@cspell/dict-tr-tr/cspell-ext.json", + "@cspell/dict-uk-ua/cspell-ext.json" + ] + } + ``` +--> + +- **RQ21:** How do I switch the package manager from `pnpm` to bun, yarn, or npm? **RA21:** Here's a variant of `scripts` for `bun`, but it not tested too much by us. Scripts presets for other package managers coming with Relivator 1.3.0. Just replace it in `package.json` (and make sure it don't miss anything). -![Relivator Landing Page Screenshot](/public/screenshot.png) + <details> + <summary>[Reveal the spoiler]</summary> -🌐 [https://relivator.bleverse.com](https://relivator.bleverse.com) + Just find and remove `packageManager` key, if present, and then replace only these specific lines by bun's alternatives: -## Project Commands + ```json + { + "scripts": { + "check:compiler": "bunx react-compiler-healthcheck@latest", + "fix:codemod-next-community": "bunx @next/codemod --dry --print", + "fix:codemod-react": "bunx codemod react/19/replace-use-form-state --target src", + "install:global": "bun add -g vercel@latest codemod@latest eslint_d@latest", + "latest:all": "bun update --latest", + "putout:dont-run-manually": "bun lint:putout . --fix", + "reli:prepare": "bun install && bun latest && bun appts", + "rm:other": "bun fse remove .million && bun fse remove .eslintcache && bun fse remove tsconfig.tsbuildinfo" + } + } + ``` -- **`pnpm db:studio`**: This command runs the Drizzle Studio on the <https://local.drizzle.studio> path. -- **`pnpm stripe:listen`**: This command runs the Stripe webhook listener and assists in setting up Stripe environment variables. You may need to have the [Stripe CLI](https://stripe.com/docs/stripe-cli) installed to run this command. -- **`pnpm latest`**: This command updates all project packages to their latest stable versions and updates tRPC to the most recent version on the next branch. This ensures we have the newest versions of TanStack Query v5. -- **`pnpm up-next:canary`**: This command updates Next.js and React to the latest versions available on their canary branches. Use this only if you are certain about its necessity. -- **`pnpm appts`**: This command performs a comprehensive check of the codebase. It sequentially executes `pnpm typecheck` to identify any TypeScript errors, `pnpm format` to format the code with Prettier (and/or with Biome — coming soon), `pnpm lint` for code linting with EsLint (most of rules in `eslint.config.ts` are disabled by default, just enable what you need) (and/or with Biome — coming soon). **NOTE**: Linting can be time-consuming, so please be patient. The command also runs `pnpm test` to check Jest tests, and finally, it executes `pnpm build`. _Thats okay if you see Clerk's warnings_ when executing `pnpm build`, this is a known issue not related to Relivator. + </details> -## Details About the Project + After you have replaced the scripts, open the project folder and close VSCode. Delete `node_modules` and `pnpm-lock.yaml`. Open the project in a terminal and run `npx nypm install`. After that, you can reopen VSCode. You're done! -[![Join the Relivator Discord](https://discordapp.com/api/guilds/1075533942096150598/widget.png?style=banner2)][bleverse-discord] +- **RQ22:** I applied the `default`/`ultimate` preset settings in VSCode, and now my IDE is slow when I save a file. **RA22:** Go to the keybindings in VSCode, set `Save without Formatting` to `Ctrl+S` (`Cmd+S`), and `File: Save` to `Ctrl+Shift+S` (`Cmd+Shift+S`). Don't worry if the code might be messy when saving without formatting. Just run pnpm appts and everything will be improved and formatted. You're welcome! P.S. You can also read the VSCode's [Performance Issues](https://github.com/microsoft/vscode/wiki/Performance-Issues) article. -We've laid the foundation—now it's your turn to dive in and speed up your development. And, yes, have **fun—think of Relivator as a sandbox**! It's like Minecraft; you can build anything with Relivator, as your creativity has no limits! Explore everything new with Next.js 14 and with many web things right here, right now—with Relivator. + <details> + <summary>[Reveal the spoiler]</summary> -You can even think of Relivator as a Next.js framework! So, finally, just grab it and enjoy! And, don’t forget: your feedback and stars mean the world to us. Smash that star button! Fork it! Your involvement lifts the project to new heights! If you have coding skills, your contributions are always welcome! + **keybindings.json** (`F1`->`>Preferences: Open Keyboard Shortcuts (JSON)`): -Run into issues? Join our repository discussions, open an issue, or DM us on: [Twitter/𝕏](https://x.com/blefnk), [Discord](https://discord.gg/Pb8uKbwpsJ), [Fiverr](https://fiverr.com/blefnk), [LinkedIn](https://linkedin.com/in/blefnk), or [Facebook](https://facebook.com/blefnk). + ```json + [{ + "command": "workbench.action.files.save", + "key": "ctrl+shift+s" + }, { + "command": "workbench.action.files.saveWithoutFormatting", + "key": "ctrl+s" + }, { + "command": "workbench.action.nextEditor", + "key": "ctrl+tab" + }, { + "command": "workbench.action.previousEditor", + "key": "ctrl+shift+tab" + }] + ``` -This project has big plans and we value all the help we can get! If you’re keen to make your own commits, check out the Project Roadmap above to see potential enhancements for the project. Also, use `Cmd/Ctrl+Shift+F` in VSCode and search for `todo:` to find spots that need attention. Please visit the **[Commits](https://github.com/blefnk/relivator/issues)** tab for more opportunities to assist. + </details> -**🔥 We're Growing Fast! A Huge Thanks to [All Our Supporters](https://github.com/blefnk/relivator/stargazers)! Check Out Our Star History:** +- **RQ23:** What does index.ts do in the server and utils folders? **RA23:** These are called barrel files. You can make imports more convenient in your project by using the barrel approach. To do this, use index.ts files to re-export items from other files. Please note: keep code that can only run on the server in the server folder. Code that can run on both the server and client sides should be kept in the utils folder. Relivator 1.2.6 currently violates this description, so we should fix it in v1.3.0. Typically, server functions look like getDoSomething. Additionally, do not import code from the server folder into .tsx files that use React Hooks (useHookName), except when the component has useTransition or similar, which allows you to perform a server action from within the client component. -[![Star History Chart](https://api.star-history.com/svg?repos=blefnk/relivator&type=Date)](https://star-history.com/#blefnk/relivator&Date) +- **RQ24:** Why do I see console.[log|info|warn|error|...] only in the browser console and not in the terminal from which the application was launched? **RA24:** If I (@blefnk) researched correctly, it is because you are calling console() in a client-side component (which has the "use client" directive at the top of the file or uses React Hooks). It looks like the terminal works as a server environment. Try calling console() in a file that does not have that directive and hooks. Or just use toasts which works nice with both on the client and the server side. -> **Note** -> Striving to be highly useful, this README contains a lot of information. Some text may be outdated and will be updated as we grow. Please let us know on the [discussion page](https://github.com/blefnk/relivator/discussions/6) if you spot any small issues like outdated info, broken links, or grammatical/spelling errors in README.md or other files. +- **RQ25:** I'm getting strange VSCode extension errors, what should I do? **RA25:** Don't worry, these are just editor anomalies. **Just restart your VSCode, and you're done.** Sometimes, due to insufficient RAM, internal extension failures, or other reasons, a particular extension might crash. Especially anomalous is the notification from TypeScript ESLint stating that it can have no more than 8 entry files (we will try to fix this in 1.3.0). Or Biome might start linting `node_modules` for some reason, which is also strange to us; our attempts to fix this so far have been unsuccessful, but we will try again in 13.0. Besides this, an extension crash might also happen if you just used `pnpm reli:setup` and didn't restart the editor. Or if you manually edited a configuration file and since autosave was enabled, the editor managed to send the configuration with syntax errors to the extension, causing everything to crash. So, restart VSCode, and everything will be fixed! If that doesn't help, check if your configuration files have any issues. -_Hint: This README.md is translated into these languages (may be outdated):_ [Polish](https://github.com/blefnk/relivator/blob/main/.github/translations/polish.md), [Ukrainian](https://github.com/blefnk/relivator/blob/main/.github/translations/ukrainian.md). +- **RQ26:** How do I change VSCode's panel position? **RA26:** Just right-click on the panel, select `Panel Position`, and choose the desired position, e.g., `Bottom`. -## Environment Variables (`.env` file) +- **RQ27:** I have the correct data (key-value) specified in the `.env` file, but a certain library, for example, Clerk, does not see this data or sees outdated data. What can I do? **RA27:** The simplest solution is to just rename your project folder, run `pnpm install`, and check if the issue is resolved. Otherwise, contact the technical support and community of the respective library. -**Refer to the [`.env.example`](https://github.com/blefnk/relivator/blob/main/.env.example) file as your guide. Simply copy data from it to a new `.env` file.** +- **RQ28:** How can I configure `pnpm` or `bun` (as package manager) for my needs? **RA28:** You can visit [this `pnpm` page](https://pnpm.io/package_json) or [this `bun` page](https://bun.sh/docs/runtime/bunfig#package-manager) in the official docs to learn more. -The `DATABASE_URL`, `NEXT_PUBLIC_DB_PROVIDER`, and `NEXT_PUBLIC_AUTH_PROVIDER` environment variables are mandatory; others are optional. You're welcome to deploy the application as-is, but ensure you verify what's necessary. Though the application will run without certain variables, missing ones may deactivate specific features. +**RQ29:** Should I modify the components by [shadcn/ui](https://ui.shadcn.com) (as of Relivator 1.2.6, they are located in the `"addons/browser/reliverse/shadcn/ui"` folder)? **RA29:** You may lose your changes if @shadcn or [Reliverse](https://github.com/orgs/reliverse/repositories) updates any of these components in the release of Relivator 1.3.x+. Therefore, the best option currently is to use, for example, the `"addons/cluster/reliverse/shadcn/ui"` folder, where you can have files that you can safely overwrite the original files with, ensuring you do not lose your changes. As an example, this folder already contains a `cluster-readme.tsx` file, which only re-exports the original `button.tsx` file. So, you can create a `button.tsx` file here and copy and paste that line into your newly created file. Alternatively, you can duplicate the code from the original file and make any modifications you want. Use `Cmd/Ctrl+Shift+H` and simply replace `addons/browser/reliverse/shadcn/ui` with `addons/cluster/reliverse/shadcn/ui` (the difference is only in the words `"browser"` and `"cluster"`). `addons/cluster` is your house; feel free to do anything you want here, mess it up or tidy it up as you wish. This is your own house, and no one has the right to take it away from you. -Ensure that default values are defined for essential environment variables. Never store secrets in the `.env.example` file. For newcomers or repo cloners, use `.env.example` as a template to create your `.env` file, ensuring it’s never committed. Update the schema in `/src/env.mjs` when adding new variables. +- **RQ30:** Which command allows me to easily manage the installation of dependencies in a project? **RA30:** `pnpm deps:install`. However, before running this script, you should manually install the essentials: -Further [information about environment variables is available here](https://nextjs.org/docs/app/building-your-application/configuring/environment-variables). + - npx nypm add typescript tsx nypm @mnrendra/read-package @clack/prompts + - npx nypm add pathe fast-npm-meta semver @types/semver redrun axios + - bun|yarn|pnpm dlx jsr add @reliverse/core (or: npx jsr add @reliverse/core) -_A cleaner and improved version of this section is coming soon. Keep an eye out!_ +- **RQ31:** I noticed a [Turborepo](https://turbo.build) file named `turbo.disabled.json`. How can I reactivate `turbo`? **RA31:** Simply remove the `.disabled` from the filename. You can also add the `"scripts"` from the `turbo.scripts.json` file to the `package.json` file (if they are not already there). + +- **RQ32:** Where can I find out more details about the Relivator and Reliverse? **RA32:** Read the current README.md file to learn more about each specific aspect of the project. You can also find more information on the project's [Discord](https://discord.gg/Pb8uKbwpsJ) server and on the [GitHub Issues](https://github.com/blefnk/relivator-nextjs-template/issues) page. + +## Details + +🌐 <https://relivator.bleverse.com> + +<img src="/public/internal/screenshot-dark.png" width="600" alt="Screenshot showing the main page of the Relivator project"> + +We've laid the groundwork—now it's time to dive in and accelerate development. And yeah, have fun! Think of Relivator and Reliverse as a sandbox! It's like Minecraft; with Relivator, you can build anything, as creativity knows no bounds! Explore all the new features of Next.js 13-15 and many other web technologies right here, right now—with Relivator. + +> ~~Minecraft~~ Reliverse is to a large degree about having unique experiences that nobody else has had. The ~~levels~~ website templates are ~~randomly~~ elegantly ~~generated~~ bootstrapped, and you can build anything you want to build yourself. © ~~Markus Persson~~ [@blefnk](https://github.com/blefnk) + +For real, if you've been exploring which Next.js starter to select for your next project–the search can end here. **If you want a POWERHOUSE**—Relivator is suitable for every scenario—then **Relivator is definitely the starter you need**. Fork it right now! Relivator incorporates numerous features found in other templates and strives to push the limits of Next.js and React capabilities. With Relivator, the opportunities are boundless! + +You can even think of Relivator as a Next.js framework! So grab it and enjoy! And don't forget: feedback and stars mean the world to us. Hit that star button! Fork it! Your involvement propels the project to new heights! If you have coding skills, contributions are always welcome! + +If you run into issues, join our repository discussions, open an issue, or DM us on: [Discord](https://discord.gg/Pb8uKbwpsJ), [Twitter/X](https://x.com/blefnk), [Fiverr](https://fiverr.com/blefnk), [LinkedIn](https://linkedin.com/in/blefnk), or [Facebook](https://facebook.com/blefnk). + +This project has big plans, and we appreciate all the help we can get! If you're eager to contribute, check out the Project Roadmap above to see potential improvements for the project. Also, use `Cmd/Ctrl+Shift+F` in VSCode and search for `TODO:` to find areas that need attention. Please visit the **[Issues](https://github.com/blefnk/relivator-nextjs-template/issues)** tab for more opportunities to help out. + +## Project Structure + +**Only a few of the files are listed here.** This section will be updated in the future versions. + +- [.vscode](https://code.visualstudio.com) + - presets + - [extensions.json](https://code.visualstudio.com/docs/editor/extension-marketplace) + - [launch.json](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_launch-configuration-attributes) + - [settings.json](https://code.visualstudio.com/docs/getstarted/settings) +- src + - [env.js](https://create.t3.gg/en/usage/env-variables) + - [middleware.ts](https://nextjs.org/docs/app/building-your-application/routing/middleware) + - ... +- [biome.json](https://biomejs.dev/reference/configuration) +- [cspell.json](https://cspell.org/configuration) +- [eslint.config.js](https://eslint.org/docs/latest/use/configure/configuration-files) +- [knip.json](https://knip.dev/reference/configuration) +- [next.config.js](https://nextjs.org/docs/app/api-reference/next-config-js) +- [package.json](https://docs.npmjs.com/cli/v10/configuring-npm/package-json) +- [README.md](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) +- [reliverse.config.ts](https://github.com/blefnk/reliverse) +- [reset.d.ts](https://totaltypescript.com/ts-reset) +- [tsconfig.json](https://typescriptlang.org/docs/handbook/tsconfig-json.html) +- [typestat.json](https://github.com/JoshuaKGoldberg/TypeStat#readme) +- ... + +## Configuration + +**First thing first, refer to the [`.env.example`](.env.example) file as the guide. Simply copy data from it to a new `.env` file.** + +*The instructions below may be outdated, so please double-check them! We will fully update this README.md with the Relivator 1.3.0 release.* + +🎉 Everything is optional in `.env` file starting from Relivator 1.2.6! You can deploy Relivator without a .env file! But ensure you verify what's necessary. Though the application will run without certain variables, missing ones may deactivate specific features. + +Ensure that default values are defined for essential environment variables. Never store secrets in the `.env.example` file. For newcomers or repo cloners, use `.env.example` as a template to create the `.env` file, ensuring it's never committed. Update the schema in `/src/env.js` when adding new variables. + +Further [information about environment variables is available here](https://nextjs.org/docs/app/building-the-application/configuring/environment-variables). + +*A cleaner and improved version of this section is coming soon. Stay tuned!* In the 1.1.0 Relivator release, the `.env.example` file was greatly simplified and will be streamlined even further in upcoming versions. We aim to ensure that unspecified values will simply deactivate related functionality without halting app compilation. -## Stripe Payments +## Payments -Refer to the [`.env.example`](https://github.com/blefnk/relivator/blob/main/.env.example) file as your guide where and how to get all the important environment variable keys for Stripe, including webhooks both for localhost and deployment. +Refer to the [`.env.example`](.env.example) file as the guide for where and how to get all the important environment variable keys for Stripe, including webhooks for both localhost and deployment. -Locally, install the [Stripe CLI](https://stripe.com/docs/stripe-cli) and run the command `pnpm stripe:listen` to initiate the Stripe webhook listener. This action connects Stripe to your account and generates a webhook key, which you can then set as an environment variable in Stripe's settings. +Locally, install the [Stripe CLI](https://stripe.com/docs/stripe-cli) and run the command `pnpm stripe:listen` to initiate the Stripe webhook listener. This action connects Stripe to the account and generates a webhook key, which you can then set as an environment variable in Stripe's settings. When testing Stripe, you can use its test data: `4242424242424242` | `12/34` | `567` | `Random Name` | `Random Country`. -Please refer to [src/app/api/webhooks/stripe/route.ts](https://github.com/blefnk/relivator/blob/main/src/app/api/webhooks/stripe/route.ts) file to learn more in the deep details how Stripe things work in the app. You can also visit the [official Stripe repository](https://github.com/stripe/stripe-node#readme), where you'll find a lot of useful information. +Please refer to the [src/app/api/webhooks/stripe/route.ts](src/app/api/webhooks/stripe/route.ts) file for more details on how Stripe works in the app. You can also visit the [official Stripe repository](https://github.com/stripe/stripe-node#readme), where you'll find a lot of useful information. + +## Database + +Relivator uses [Drizzle ORM](https://orm.drizzle.team) for database management. By default, the project uses [Neon](https://neon.tech) (Serverless) with [PostgreSQL](https://neon.tech/docs/postgresql/introduction) as the database provider. The project has already followed [Drizzle's guide](https://orm.drizzle.team/learn/tutorials/drizzle-with-neon) on how to set up Drizzle with Neon Postgres. + +**August 4, 2024: Hot Update**: + +If you use `neon` as your database provider, you no longer need `pnpm db:studio`; simply use Drizzle Studio on [Neon's website](https://neon.tech) 🎉 -## Database Support +For development databases without important data, you can use `pnpm db:push`. For production databases containing important data, it is recommended to use `pnpm db:generate` followed by `pnpm db:migrate`. -Relivator is designed to effortlessly support both `MySQL` and `PostgreSQL` databases. Although it comes with MySQL and [PlanetScale](https://planetscale.com) configured as the default database provider, switching to PostgreSQL provided by [Neon](https://neon.tech)/[Vercel](https://vercel.com/storage/postgres)/[Railway](https://railway.app) — is really simple as pie. To do so, just update the `NEXT_PUBLIC_DB_PROVIDER` key in your `.env` file to `neon`/`vercel`/`railway` accordingly. While Relivator is optimized for these providers, any others compatible with Drizzle and NextAuth.js might also work, though they may require some additional setup. +> Drizzle Kit lets you alter you database schema and rapidly move forward with a [pnpm db:push](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push) command. That’s very handy when you have remote databases like Neon, Planetscale or Turso. The 'push' command is ideal for quickly testing new schema designs or changes in a local development environment, allowing fast iterations without the overhead of managing migration files. © [Drizzle Team](https://orm.drizzle.team/learn/tutorials/drizzle-with-neon) -To initiate a new database or propagate schema changes, execute the `pnpm mysql:push` or `pnpm pg:push` command. This ensures that all drafts made to the schema files—found under `src/data/db/*`—are mirrored in your selected database provider. +**Drizzle Team**: If you want to iterate quickly during local development or if your project doesn’t require migration files, Drizzle offers a useful command called drizzle-kit push. **When do you need to use the ‘push’ command?** **1.** During the prototyping and experimentation phase of your schema on a local environment. **2.** When you are utilizing an external provider that manages migrations and schema changes for you (e.g., PlanetScale). **3.** If you are comfortable modifying the database schema before your code changes can be deployed. -For database migrations, utilize the `mysql:generate`/`pg:generate`, review the `drizzle` folder to ensure everything correct (execute `db:drop` if not), and run the `pnpm:migrate` command when you are ready. +**Note**: NEXT_PUBLIC_DB_PROVIDER was removed in Relivator v1.2.6. To switch the provider from Neon, modify `drizzle.config.ts`. To use MySQL or LibSQL providers, update the files inside `src/db`. An automatic switcher is coming in Relivator version 1.3.x. -Ensure you do not manually delete files from the `drizzle` directory. If a migration needs to be reversed, employ the [`pnpm db:drop` command](https://orm.drizzle.team/kit-docs/commands#drop-migration) to manage this in a controlled way. +*The instructions below may be outdated, so please double-check them! We will fully update this README.md with the Relivator 1.3.0 release.* -In the release of Relivator v1.1.0, we made our best efforts to provide simultaneous support for both MySQL and PostgreSQL for the Drizzle ORM. In future releases, we aim to integrate Prisma ORM to this project as well. Thanks to it, the project will be even more inclusive to everyone. +Relivator is designed to effortlessly support both MySQL and PostgreSQL databases. While PostgreSQL and [Neon](https://neon.tech) are the default configurations, switching to MySQL provided by [Railway](https://railway.app?referralCode=sATgpf) or [PlanetScale](https://planetscale.com), or to PostgreSQL provided by [Railway](https://railway.app?referralCode=sATgpf) or [Vercel](https://vercel.com/storage/postgres) is straightforward. Adjust the database configuration inside [drizzle.config.ts](./drizzle.config.ts) and the `src/db/*` files accordingly. Although Relivator is optimized for these providers, other providers compatible with Drizzle and NextAuth.js might also work with some additional setup. Full SQLite support is coming soon. -By default we ensure that every database system has everything the same by using `NEXT_PUBLIC_DB_PROVIDER` env variable and by exporting things in the `src/data/db/index.ts` file. When you decide which database provider is best suit your needs, you can safely comment out or remove unneeded providers in the `switch-case` of this file, then related schema files can be removed as well; note that some small additional work may be also required. +To set up the `DATABASE_URL` in the `.env` file, refer to `.env.example`. Initiate a new database or propagate schema changes by executing the `pnpm db:push` command. This ensures that all changes made to the schema files in `src/db/*` are mirrored in the selected database provider. -### Product Categories and Subcategories +For database migrations, use the `pnpm db:generate` command, review the `drizzle` folder to ensure everything is correct, and run the `pnpm db:migrate` command when ready. If necessary, use `pnpm db:drop` to manage reversals in a controlled way. -To edit product categories, please refer to the [MySQL](https://github.com/blefnk/relivator/blob/main/src/data/db/schema/mysql.ts#L167C5-L174) or [PostgreSQL](https://github.com/blefnk/relivator/blob/main/src/data/db/schema/pgsql.ts#L24-L29) schema. +If you used Relivator before v1.2.6, you may remove the `drizzle` folder inside the root directory. **Possible outdated information:** Do not manually delete files from the `drizzle` directory. Instead, use the [`pnpm db:drop` command](https://orm.drizzle.team/kit-docs/commands#drop-migration) if a migration needs to be reversed. -After editing these files, don't forget to run `pnpm mysql:push` or `pnpm pg:push` to apply the changes. +We ensure consistent database configuration by using the setup inside `drizzle.config.ts` and exporting configurations in `src/db/index.ts` and `src/db/schema/index.ts`. When selecting a database provider, comment out or remove unneeded providers in the `switch-case` of these files and remove related schema files as necessary. Additional adjustments in other files may also be required. An automatic switcher is coming soon in the Relivator v1.3.0 release. -Then, simply update the category names and subcategories in the [products file](https://github.com/blefnk/relivator/blob/main/src/server/config/products.ts#L23) accordingly. +**Historical context**: In Relivator v1.1.0, we aimed to provide simultaneous support for both MySQL and PostgreSQL for Drizzle ORM. In future releases, we plan to integrate Prisma ORM, making the project even more inclusive. -### Additional Notes About Stripe +## Product Categories and Subcategories -The Stripe webhook API route does not need to be invoked explicitly within your application, such as after a user selects a subscription plan or makes a purchase. Webhooks operate independently of user actions on the frontend and serve as a means for Stripe to relay events directly to your server. +To edit product categories, please refer to the `MySQL`, `PostgreSQL`, or `LibSQL` corresponding schema file in the `src/db/schema` folder. -When an event occurs on Stripe's end, such as a successful payment, Stripe generates an event object. This object is then automatically sent to the endpoint you've specified, either in your Stripe Dashboard or, for testing purposes, in your `package.json` via the Stripe CLI. Finally, your server's API route receives the event and processes it accordingly. +After editing these files, don't forget to run `pnpm db:push` to apply the changes. Or run `pnpm generate` to create a sharable SQL, which another developers may apply with `pnpm migrate` to edit theirs database tables easily. + +Then, simply update the category names and subcategories in the [products file](src/config/products.ts) accordingly. + +## Additional Notes About Stripe + +*The instructions below may be outdated, so please double-check them! We will fully update this README.md with the Relivator 1.3.0 release.* + +The Stripe webhook API route does not need to be invoked explicitly within the application, such as after a user selects a subscription plan or makes a purchase. Webhooks operate independently of user actions on the frontend and serve as a means for Stripe to relay events directly to the server. + +When an event occurs on Stripe's end, such as a successful payment, Stripe generates an event object. This object is then automatically sent to the endpoint you've specified, either in the Stripe Dashboard or, for testing purposes, in the `package.json` via the Stripe CLI. Finally, the server's API route receives the event and processes it accordingly. For example, when a user selects a subscription plan, you would typically first use Stripe's API to create either a `Payment Intent` or `Setup Intent` object. This action can be executed either on the client-side or the server-side. The frontend then confirms the payment using Stripe.js, thereby completing the payment or subscription setup process. -Your webhook is automatically triggered based on these events. There's no need to manually "call" the webhook route; Stripe manages this for you according to your settings in the Stripe Dashboard or in your `package.json` for local testing. +The webhook is automatically triggered based on these events. There's no need to manually "call" the webhook route; Stripe manages this for you according to the settings in the Stripe Dashboard or in the `package.json` for local testing. -After deploying your app, don't forget to specify the webhook URL in your Stripe Dashboard. Navigate to the Webhooks section and enter the following URL: `https://yourdomain.com/api/webhooks/stripe`. +After deploying the app, don't forget to specify the webhook URL in the Stripe Dashboard. Navigate to the Webhooks section and enter the following URL: `https://thedomain.com/api/webhooks/stripe`. -In summary, there's no need to specify the path to your Stripe API route where the user selects a subscription plan. The webhook mechanism operates independently and is triggered automatically by Stripe. +In summary, there's no need to specify the path to the Stripe API route where the user selects a subscription plan. The webhook mechanism operates independently and is triggered automatically by Stripe. ## Internationalization -_Stay tuned for further expansions to this section in the future._ +*Stay tuned for further expansions to this section in the future.* + +*The instructions below may be outdated, so please double-check them! We will fully update this README.md with the Relivator 1.3.0 release.* -Multilingualism at Bleverse is revered. We adore discussing it and plan to delve into the topic of Next.js 14 App Router internationalization in future writings. +Multilingualism at Bleverse Reliverse vision is revered. We adore discussing it and plan to delve into the topic of Next.js 15 App Router internationalization in future writings. -Presently, all languages are machine-translated. Future revisions by native speakers are planned. Note that i18n messages from another one of our open-source projects are currently present and will be removed shortly. +Presently, all languages are machine-translated. Future revisions by native speakers are planned. -_Currently not available._ Use `pnpm lint:i18n` to verify your i18n files. The tool attempts to rectify issues when possible, offering features like ascending sort. No output means everything is in order. +useTranslations works both on the server and client; we only need the getTranslations on async components. -We are using _next-intl_ for internationalization. Sometime we can use beta/rc versions as needed. Find more information about it [here](https://next-intl-docs.vercel.app/blog/next-intl-3-0) and [here](https://github.com/amannn/next-intl/pull/149). +*Currently not available.* Use `pnpm lint:i18n` to verify the i18n files. The tool attempts to rectify issues when possible, offering features like ascending sort. No output means everything is in order. + +We are using *next-intl* for internationalization. Sometime we can use beta/rc versions as needed. Find more information about it [here](https://next-intl-docs.vercel.app/blog/next-intl-3-0) and [here](https://github.com/amannn/next-intl/pull/149). ### How to add a new language -_The process described below will be simplified and automated in the future. Please let us know if you have any further questions regarding the current process for adding languages._ +*The process described below will be simplified and automated in the future. Please let us know if you have any further questions regarding the current process for adding languages.* -1. We will need an [i18n code](https://saimana.com/list-of-country-locale-code/) (in the format `language-country`; the language code alone is sufficient, but it's not optimal for SEO). For example, let's take Chinese, for which I know the codes _zh-cn/zh-tw/zh-hk_ are used. -2. Open the `src/data/i18n` folder and create a `zh-cn.json` file with the example content: `{ "landing": { "heading": "建立更高效、更吸引人且更有利可图的在线商店:使用 Relivator" } }`. +1. We will need an [i18n code](https://saimana.com/list-of-country-locale-code/) (in the format `language-country`; the language code alone is sufficient, but it's not optimal for SEO). For example, let's take Chinese, for which I know the codes *zh-cn/zh-tw/zh-hk* are used. +2. Open the `messages` folder and create a `zh-cn.json` file with the example content: `{ "metadata": { "description": "建立更高效、更吸引人且更有利可图的在线商店:使用 Relivator" } }`. 3. Now open `src/i18n.ts` and add `"zh-cn": zh_cn` with the appropriate `import` at the top. 4. In the file `src/navigation.ts`, add the corresponding values to `locales` and `labels`. 5. Run `pnpm dev` and review the landing page header. If it appears correctly, you're ready to go. 6. Optionally, I recommend using the VSCode extension [i18n Ally](https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally), which makes machine translation easy. -7. Also optionally, install [specific CSpell language](https://github.com/streetsidesoftware/cspell-dicts#language-dictionaries) for full support of this language in VSCode (when using the "[Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker)" extension). If your language is not available, try to find a word dictionary file on the web or make one yourself (see CSpell docs). - -By the way, **if the country flag is not displayed**: open `src/localization-main.tsx`, go to _LocaleFlagIcon_ and add your `else if`. Please visit the [flag-icons library website](https://flagicons.lipis.dev/) to see the code for the missing icon. The example for _zh-cn_ will be: `} else if (baseLocale === "zh") { return <span aria-hidden="true" className="fi fi-cn mr-2" />; }` +7. Also optionally, install [specific CSpell language](https://github.com/streetsidesoftware/cspell-dicts#language-dictionaries) for full support of this language in VSCode (when using the "[Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker)" extension). If the language is not available, try to find a word dictionary file on the web or make a new one (see CSpell docs). -Please be aware that both the "i18n Ally" VSCode extension and manual systems like "Google Translate" may incorrectly translate variables. If you encounter an error like this: -Original Message: 'The intl string context variable "类别" was not provided to the string "购买最好的{类别}"'. -This error occurs because we have {类别}, but it should be {category}. To verify the correct variable, refer to the en-us.json file.Certainly! Here's a grammatically corrected version of your text: +By the way, **if the country flag is not displayed**: open `src/localization-main.tsx`, go to *LocaleFlagIcon* and add the `else if`. Please visit the [flag-icons library website](https://flagicons.lipis.dev/) to see the code for the missing icon. The example for *zh-cn* will be: `} else if (baseLocale === "zh") { return <span aria-hidden="true" className="fi fi-cn mr-2" />; }` Please be aware that both the "i18n Ally" VSCode extension and manual systems like "Google Translate" may incorrectly translate variables. If you encounter an error like this: Original Message: 'The intl string context variable "类别" was not provided to the string "购买最好的{类别}"'. -This error occurs because we have {类别}, but it should be {category}. To verify the correct variable, refer to the en-us.json file. -So the correct version for this specific case will be: - -```txt -"categories": { - "buyProducts": "购买 ${category} 类别的产品", - "buyFromCategories": "从最好的商店购买 {category}", - "buyTheBest": "购买最好的 {category}" -}, +This error occurs because we have {类别}, but it should be {category}. To verify the correct variable, refer to the en-us.json file.Certainly! So the correct version for this specific case will be: + +```json +{ + "categories": { + "buyFromCategories": "从最好的商店购买 {category}", + "buyProducts": "购买 ${category} 类别的产品", + "buyTheBest": "购买最好的 {category}" + } +} ``` -**Currently supported locales (you can add your own manually):** +**Currently supported locales (you can add the own manually):** -de-DE, en-US, es-ES, fa-IR, fr-FR, hi-IN, it-IT, pl-PL, tr-TR, uk-UA, zh-CN. +- de, en, es, fa, fr, hi, it, pl, tr, uk, zh. +- de-DE, en-US, es-ES, fa-IR, fr-FR, hi-IN, it-IT, pl-PL, tr-TR, uk-UA, zh-CN. ## Principles, Design Decisions, Code Insights, Recommendations -_We're continuously improving this section. Contributions are welcomed!_ - -Our starter aims to be a rich resource for developers at all stages of their journey. Within the comment blocks and dedicated sections at the end of select files, you'll find valuable insights and clarifications on a wide array of topics. Your contributions to enhancing these educational nuggets are highly encouraged! - -**Principles (W.I.P):** - -- [ ] Prettier's principle over linters related to developer experience ([source](https://prettier.io/docs/en/integrating-with-linters.html#notes)): "You end up with a lot of red squiggly lines in your editor, which gets annoying. Prettier is supposed to make you forget about formatting – and not be in your face about it!" -- [ ] Every file and component should be built consciously, by using [KISS/DRY/SOLID/YAGNI principles](https://blog.openreplay.com/applying-design-principles-in-react), with a certain sense of intelligence, with performance in mind. -- [ ] We need to think of the project as if it were a planet with its own continents, countries, cities, rooms, individuals, entities etc. - -**Highly-Recommended VSCode Extensions:** - -1. [Better Comments](https://marketplace.visualstudio.com/items?itemName=aaron-bond.better-comments) -2. [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) -3. [Error Lens](https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens) -4. [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) -5. [GitHub Copilot](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot) -6. [i18n Ally](https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally) -7. [JavaScript and TypeScript Nightly](https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-typescript-next) -8. [Markdown All in One](https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one) -9. [markdownlint](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint) -10. [POP! Icon Theme](https://marketplace.visualstudio.com/items?itemName=mikekscholz.pop-icon-theme) -11. [Prettier—Code formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) -12. [Pretty TypeScript Errors](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors) -13. [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) -14. [TailwindCSS Tune](https://marketplace.visualstudio.com/items?itemName=omkarbhede.tailwindcss-tune) -15. [TypeScript Essential Plugins](https://marketplace.visualstudio.com/items?itemName=zardoy.ts-essential-plugins) +> «There are a lot of impractical things about owning a ~~Porsche~~ Relivator. But they're all offset by the driving experience. It really is unique. ~~Lamborghinis~~ create-t3-app and ~~Ferraris~~ Vercel Store come close. And they are more powerful in specific cases, but they don't handle like a ~~Porsche~~ Relivator.» © ~~Kevin O'Leary~~ [@blefnk](https://github.com/blefnk) -<details> - <summary>Why "TypeScript Essential Plugins" is Recommended</summary> +*We're continuously improving this section. Contributions are welcomed!* -«Feature-complete TypeScript plugin that improves every single builtin feature such as completions, definitions, references and so on, and also adds even new TypeScript killer features, so you can work with large codebases faster! We make completions more informative. Definitions, references (and sometimes even completions) less noisy. And finally our main goal is to provide most customizable TypeScript experience for IDE features.» © [VSCode Extension Repository](https://github.com/zardoy/typescript-vscode-plugins#readme) +Our starter aims to be a rich resource for developers at all stages of their journey. Within the comment blocks and dedicated sections at the end of select files, you'll find valuable insights and clarifications on a wide array of topics. Contributions to enhancing these educational nuggets are highly encouraged! -Note: You can configure extension settings by opening VSCode Settings UI and searching for `@ext:zardoy.ts-essential-plugins` there. +**Principles (W.I.P):** -</details> +- [ ] Prettier's principle over linters related to developer experience ([source](https://prettier.io/docs/en/integrating-with-linters.html#notes)): "You end up with a lot of red squiggly lines in the editor, which gets annoying. Prettier is supposed to make you forget about formatting – and not be in the face about it!" +- [ ] Every file and component should be built consciously, using [KISS/DRY/SOLID/YAGNI principles](https://blog.openreplay.com/applying-design-principles-in-react) with a certain sense of intelligence, and with performance in mind. +- [ ] We need to think of the project as if it were a planet with its own continents, countries, cities, rooms, individuals, entities, etc. **Advanced Environment Variables:** -The `.env.example` file covers all the essential variables for a fully functional website, tailored for beginners. However, if you require advanced configurations, you can modify any value in `.env` file as needed. +The `.env.example` file covers all the essential variables for a fully functional website, tailored for beginners. However, if you require advanced configurations, you can modify any value in the `.env` file as needed. **About the Plugins Folder:** -This folder contains optional plugins for Relivator. Developed by @blefnk and other contributors, these plugins extend functionality and provide additional features. If you find that certain plugins are not beneficial for your project, feel free to remove their corresponding folders. +This folder contains optional plugins for Relivator. Developed by @blefnk and other contributors, these plugins extend functionality and provide additional features. If you find that certain plugins are not beneficial for the project, feel free to remove their corresponding folders. **Function Over Const for Components:** -We advocate the use of the `function` keyword instead of `const` when defining React components. Using `function` often improves stack traces, making debugging easier. Additionally, it makes code semantics more explicit, thereby making it easier for other developers to understand your intentions. +We advocate the use of the `function` keyword instead of `const` when defining React components. Using `function` often improves stack traces, making debugging easier. Additionally, it makes code semantics more explicit, thereby making it easier for other developers to understand the intentions. **Personal Recommendations:** -We advise regularly clearing your browser cache and deleting the `.next` folder to ensure optimal performance and functionality. +We advise regularly clearing the browser cache and deleting the `.next` folder to ensure optimal performance and functionality. Currently, we don’t utilize Contentlayer due to its instability with Windows. Therefore, we're exploring options for its usage in the `.env` configuration file. Future plans might involve developing our own solution for content writing. Integration with external providers, such as Sanity, may also be a future feature, with corresponding enable/disable options. +NOTE from the [Contentlayer Issues Page](https://github.com/contentlayerdev/contentlayer/issues/313#issuecomment-1305424923): Contentlayer doesn't work well with `next.config.mjs`, so you need to have `next.config.js`. Other libraries may also require this. If you're sure you need `.mjs` and don't plan to use Contentlayer, rename it. + **Project Configuration:** -The `src/app.ts` file hosts critical configurations to modify website contents and settings, enabling you to: +The `src/app.ts` file hosts main configurations to modify website contents and settings, enabling you to: - Control the content presented on the website. - Adjust various settings, such as deactivating the theme toggle. - Manage generic site-wide information. -Customize this file as per your requirements. +Customize this file as per the requirements. **Authentication:** @@ -352,11 +697,34 @@ You can configure available sign-in providers for Clerk in the `src/app.ts` file Please remember that Clerk fully works with third-party services like "Google PageSpeed Insight" only when domain and live keys are used. -_This section will be implemented soon._ +*This section will be implemented soon.* + +**TypeScript Config:** + +In the `tsconfig.json` file you can set the options for the TypeScript compiler. You can hover over on each option to get more information about. Hint: You can also press Shift+Space to get auto-completion. Learn more by checking the official TypeScript documentation: @see <https://typescriptlang.org/docs/handbook/tsconfig-json> @see <https://totaltypescript.com/tsconfig-cheat-sheet>. + +Next.js has built-in support for TypeScript, using plugin below. But while you use `pnpm build`, it stops on the first type errors. So you can use `pnpm typecheck` to check all type warns/errors at once. + +Config includes Atomic CSS plugin in the style attribute. Type-safe static styles with theming, responsive variant support, and no bundler integration. @see <https://github.com/tokenami/tokenami#readme>. + +You can enable strict type checking in MDX files by setting `mdx.checkMdx` to true. + +These options below can be dangerously set to false, while you're incrementally move to full type-safety. + +```json +{ + "alwaysStrict": true, + "noImplicitAny": false, + "strict": true, + "strictNullChecks": true, + "strictPropertyInitialization": true, + "verbatimModuleSyntax": true +} +``` **How to Deploy the Project:** -Please check the _How to Install and Get Started_ section before making the initial deployment. +Please check the *How to Install and Get Started* section before making the initial deployment. Consult the deployment guides for [Vercel](https://create.t3.gg/en/deployment/vercel), [Netlify](https://create.t3.gg/en/deployment/netlify), and [Docker](https://create.t3.gg/en/deployment/docker) for further details. The project has only been tested on Vercel; please inform us if you encounter issues with other deployment services. @@ -368,89 +736,282 @@ By default, this project includes components from various libraries, as well as W.I.P. — Use `pnpm css` to watch for [CSS tokens](https://blog.devgenius.io/link-figma-and-react-using-figma-tokens-89e6cc874b4d) in accordance with the project's design system. [Tokenami](https://github.com/tokenami/tokenami#readme) and Figma are anticipated to be utilized for this concept. For additional information, refer to points #33 and #90 in the Relivator's Roadmap. -**Bun Things Compatibility:** +**Package Manager Compatibility:** -`Relivator` can already harness some fantastic **[`Bun`](https://bun.sh)** features. For this starter, we currently recommend using `pnpm`. Full Bun support and compatibility will be shipped as soon as Windows binaries are available. _Section expansion coming soon._ +`Relivator` can already harness some fantastic **[`Bun`](https://bun.sh)** features. For this starter, we currently recommend using `pnpm`. Full pnpm support and compatibility will be shipped as soon as [Reliverse](https://github.com/blefnk/reliverse)'s [Versator](https://github.com/blefnk/versator) achieves full similarity with Relivator. *Section expansion coming soon.* -**Typical App Workflow (Coming Soon):** +**Recommended Things to Learn:** -A comprehensive guide detailing the typical application workflow will be implemented soon. For now, here's a quick overview: +1. [The Detailed Git Guide](https://github.com/blefnk/relivator-nextjs-template/blob/main/.github/GITGUIDE.md) by [Nazar Kornienko @Blefnk](https://github.com/blefnk) +2. [Introduction to Next.js and React](https://youtube.com/watch?v=h2BcitZPMn4) by [Lee Robinson](https://twitter.com/leeerob) +3. [Relivator: Next.js 15 Starter (Release Announce of Relivator on Medium)](https://cutt.ly/awf6fScS) by [Nazar Kornienko @Blefnk](https://github.com/blefnk) +4. [Welcome to the Wild World of TypeScript, Mate! Is it scary?](https://cutt.ly/CwjVPUNu) by [Nazar Kornienko @Blefnk](https://github.com/blefnk) +5. [React: Common Mistakes in 2023](https://docs.google.com/presentation/d/1kuBeSh-yTrL031IlmuwrZ8LvavOGzSbo) by [Cory House](https://twitter.com/housecor) +6. [Thoughts on Next.js 13, Server Actions, Drizzle, Neon, Clerk, and More](https://github.com/Apestein/nextflix/blob/main/README.md#overall-thoughts) by [@Apestein](https://github.com/Apestein) +7. [Huge Next-Multilingual Readme About i18n](https://github.com/Avansai/next-multilingual#readme) by [@Avansai](https://github.com/Avansai) +8. [Applying Design Principles in React](https://blog.openreplay.com/applying-design-principles-in-react) by [Jeremiah (Jerry) Ezekwu](https://blog.openreplay.com/authors/jeremiah-\(jerry\)-ezekwu/) +9. [The Power of Prototyping Code](https://medium.com/@joomiguelcunha/the-power-of-prototyping-code-55f4ed485a30) by [João Miguel Cunha](https://medium.com/@joomiguelcunha) +10. [Software Prototyping](https://en.wikipedia.org/wiki/Software_prototyping) on Wikipedia +11. [TDD: Test-driven development](https://en.wikipedia.org/wiki/Test-driven_development) on Wikipedia +12. [React 19 RC Announcement](https://react.dev/blog/2024/04/25/react-19) by [React](https://react.dev) -1. _Run Development Server_: `pnpm dev` -2. _Environment Configuration_: `.env` -3. _Middleware Setup_: `middleware.ts` -4. _Additional Steps_: Stay tuned... +*More learning resources can be found within the files of this repository.* -**FAQ (Frequently Asked Questions):** +## Contributing -_Q:_ How to grant admin rights to myself or to another user? -_A:_ Just run `pnpm db:studio`, navigate to the `acme_user` table and set `role: admin` for the user you need. In the future, if you have admin rights, you will be able to change the user privilegies of selected users directly from the frontend admin page. +*This section will be enhanced soon with simpler steps to get everything ready.* -_Q:_ What does the `DEV_DEMO_NOTES` environment variable mean? -_A:_ Just don't use it. It is used only on the official [Relivator demo website](https://relivator.bleverse.com) to showcase certain features that are not needed in real-world applications. +Contributions are warmly welcomed! We express our gratitude to everyone who has contributed to this repository. the contributions will be recognized. If you have any questions or suggestions, please open an issue. For more information, see the [contributing guide](.github/CONTRIBUTING.md). -_Q:_ I'm using PlanetScale as my database provider. After taking a break from the project, I'm now encountering "unable to connect to branch" error in the console. How can I fix this? -_A:_ Simply go to the PlanetScale dashboard and click on the `wake up` button. Please contact us in case if your database is not asleep and the problem still persists. +Please visit [this special wiki page](https://github.com/blefnk/relivator-nextjs-template/wiki/Project-Credits-&-Contributors) to view the full list of credits and contributors. To contribute to Relivator, follow these steps: -**Recommended Things to Learn:** +1. Begin by reading the "How to Install and Get Started" section on the top of this repository, and by reading [CONTRIBUTING.md](.github/CONTRIBUTING.md) page. +2. Create a branch: `git checkout -b <branch_name>`. +3. Make and commit the changes: `git commit -m '<commit_message>'` +4. Push to the original branch: `git push origin <branch_name>` +5. Submit the pull request. + +Alternatively, check the GitHub docs on [how to create a pull request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request). + +## Roadmap + +*The roadmap below may be outdated. We will fully update this README.md with the Relivator 1.3.0 release.* + +*The roadmap below outlines the key features and improvements planned for this Next.js Reliverse starter and for Reliverse CLI. `Items not marked may already be configured` but might not have been thoroughly tested. If you spot any issues, please open an issue.* + +- [x] 1. Build on [Next.js 15](https://nextjs.org) [App Router](https://nextjs.org/docs/app) & [Route Handlers](https://nextjs.org/docs/app/building-the-application/routing/route-handlers), with [Million Lint](https://million.dev) and [Turbopack](https://turbo.build/pack) support (with optional [Turborepo v2](https://turbo.build/blog/turbo-2-0) for faster builds). Utilize [React 19](https://react.dev) (with the new [React Compiler](https://react.dev/learn/react-compiler) and [eslint-plugin-react-compiler](https://react.dev/learn/react-compiler#installing-eslint-plugin-react-compiler)), [TailwindCSS](https://tailwindcss.com), and [TypeScript 5](https://typescriptlang.org) as core technologies. +- [x] 2. Implement [Drizzle ORM](https://orm.drizzle.team) to support **both MySQL and PostgreSQL** databases, and integrate with [Neon](https://neon.tech), [Railway](https://railway.app?referralCode=sATgpf), [PlanetScale](https://planetscale.com), and [Vercel](https://vercel.com) services. +- [x] 3. Configure `next.config.js` with i18n, MDX, with both [Million.js Compiler & Million Lint](https://million.dev/blog/lint-rc) support. Enable these cool experiments: after, forceSwcTransforms, instrumentationHook (disabled by default), mdxRs, optimisticClientCache, [optimizePackageImports](https://nextjs.org/docs/app/api-reference/next-config-js/optimizePackageImports), optimizeServerReact, [ppr (Partial Prerendering)](https://nextjs.org/docs/app/api-reference/next-config-js/partial-prerendering), reactCompiler (disabled by default), serverMinification, turbo. +- [x] 4. Aim for thorough documentation and a beginner-friendly approach throughout the project. +- [x] 5. Configure and comment on `middleware.ts` for i18n and next-auth. +- [x] 6. Set up a Content-Security-Policy (CSP) system to prevent XSS attacks (disabled by default). +- [x] 7. Provide well-configured VSCode settings and recommended extensions (set `"extensions.ignoreRecommendations"` to `true` if you don't want to see the recommendations). +- [x] 8. Optimize the [Next.js Metadata API](https://nextjs.org/docs/app/building-the-application/optimizing/metadata) for SEO, integrating file-system handlers. +- [x] 9. Integrate a TailwindCSS screen size indicator for local project runs. +- [x] 10. Implement extensive internationalization in 11 languages (English, German, Spanish, Persian, French, Hindi, Italian, Polish, Turkish, Ukrainian, Chinese) using the [next-intl](https://next-intl-docs.vercel.app) library, which works on both server and client, and include support for `next dev --turbo`. +- [x] 11. Implement authentication through **both [Clerk](https://clerk.com) and [NextAuth.js](https://authjs.dev)**. +- [x] 12. Implement [tRPC](https://trpc.io) and [TanStack Query](https://tanstack.com/query) (with [React Normy](https://github.com/klis87/normy#readme)) for advanced server and client data fetching. +- [x] 13. Establish a user subscription and checkout system using [Stripe](https://github.com/stripe/stripe-node#readme). +- [x] 14. Ensure type-safety validations for project schemas and UI fields using the [zod](https://zod.dev) library. +- [x] 15. Employ [ESLint v9](https://eslint.org) with [TypeScript-ESLint v8](https://typescript-eslint.io) and configure `eslint.config.js` (**Linting can take some time, so please be patient**) to work with both [Biome](https://biomejs.dev) ~~and [Prettier](https://prettier.io) (including the Sort Imports Prettier plugin)~~ for readable, clean, and safe code. **Currently not available | TODO:** use `pnpm ui:eslint` to open the [ESLint Flat Config Viewer](https://github.com/antfu/eslint-flat-config-viewer#eslint-flat-config-viewer) UI tool. **Note:** Starting Relivator 1.3.0 Prettier can be added manually by running `reliverse` command (read [the announcement](https://github.com/blefnk/relivator-nextjs-starter/issues/36)). +- [x] 16. Elegantly implement the font system, utilizing [Inter](https://rsms.me/inter) and additional typefaces. +- [x] 17. Develop a storefront, incorporating product, category, and subcategory functionality. +- [x] 18. Design a modern, cleanly composed UI using [Radix](https://radix-ui.com), with attractive UI components from [shadcn/ui](https://ui.shadcn.com). +- [x] 19. Compose a comprehensive, beginner-friendly `README.md`, including descriptions of [environment variables](https://nextjs.org/docs/basic-features/environment-variables). +- [ ] 20. Realize blog functionality through the use of MDX files. +- [ ] 21. Use absolute paths everywhere where applied in the project. The project has a predictable and consistent import logic, no unnecessary use of things like `import * as React`. +- [ ] 22. Use [Kysely](https://kysely.dev) with Drizzle to achieve full TypeScript SQL query builder type-safety. +- [ ] 23. Translate README.md and related files into more languages. +- [ ] 24. Transform beyond a simple e-commerce store to become a universal website starter. +- [ ] 25. Tidy up `package.json` with correctly installed and orderly sorted packages in `dependencies` and `devDependencies`. +- [ ] 26. The project author should publish a series of detailed videos on how to use this project. There should also be some enthusiasts willing to publish their own videos about the project on their resources. +- [ ] 27. Reduce the number of project packages, config files, and etc., as much as possible. +- [ ] 28. Reduce HTML tag nesting and ensure correct usage of HTML tags whenever possible. +- [ ] 29. Prioritize accessibility throughout, for both app user UI (User Interface) and UX (User Experience), as well as developers' DX (Developer Experience). Maintain usability without compromising aesthetics. +- [ ] 30. Prefer using [const-arrow](https://freecodecamp.org/news/when-and-why-you-should-use-es6-arrow-functions-and-when-you-shouldnt-3d851d7f0b26) and [type](https://million.dev/docs/manual-mode/block) over [function](https://freecodecamp.org/news/the-difference-between-arrow-functions-and-normal-functions) and [interface](https://totaltypescript.com/type-vs-interface-which-should-you-use) where applicable, and vice versa where applicable correspondingly, with using helpful ESLint [arrow-functions](https://github.com/JamieMason/eslint-plugin-prefer-arrow-functions#readme) plugin, to maintain readable and clean code by adhering to specific [recommendations](https://youtu.be/nuML9SmdbJ4) for [functional programming](https://toptal.com/javascript/functional-programming-javascript). +- [ ] 31. Optimize all app elements to improve dev cold start and build speeds. +- [ ] 32. Move each related system to its special folder (into the `src/core` folder), so any system can be easily removed from the project as needed. +- [ ] 33. Move component styles to .css or .scss files, or use packages that provide "syntactic sugar" for styles in .tsx files by using [tokenami](https://github.com/tokenami/tokenami#readme) CSS library. Implement possibility to implement [Figma Tokens System](https://blog.devgenius.io/link-figma-and-react-using-figma-tokens-89e6cc874b4d) to work seamlessly with the project. Tip: go to point #90 of this roadmap to learn more about new ways to use CSS-in-JS. +- [ ] 34. Migrate to NextAuth.js' [next-auth@beta](https://npmjs.com/package/next-auth?activeTab=versions) ([discussions](https://github.com/nextauthjs/next-auth/releases/tag/next-auth%405.0.0-beta.4)), and to [React 19](https://19.react.dev/blog/2024/04/25/react-19). +- [ ] 35. Manage email verification, newsletter sign-ups, and email marketing via [Resend](https://resend.com) and [React Email](https://react.email). +- [ ] 36. Make sure each page and the middleware are green or yellow, but not red, upon build in the development terminal. +- [ ] 37. Make each environment variable optional, allowing the app to operate without anything configured, simply omitting specific code sections as necessary. +- [ ] 38. Keep the project on the best possible way of writing good and clean code, by following guidelines like [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript/tree/master/react) / [Airbnb React/JSX Style Guide](https://github.com/airbnb/javascript/tree/master/react). Use `??` (nullish coalescing) everywhere instead of `||` (logical OR) (unless there's a good reason to use it in the specific cases) – [why we should use nullish coalescing](https://stackoverflow.com/questions/61480993/when-should-i-use-nullish-coalescing-vs-logical-or); (Is there any ESLint rule/plugin for that?). +- [ ] 39. Keep the project free from things like `@ts-expect-error`, `eslint-disable`, `biome-ignore`, and others related not very safety things. +- [ ] 40. Keep the cookie count as low as possible, prepare for a cookie-free future, implement cookie management and notifications. +- [ ] 41. Introduce a comment system for products, including Review and Question types. +- [ ] 42. Integrate valuable things from [Next.js' Examples](https://github.com/vercel/next.js/tree/canary/examples) into this project. +- [ ] 43. Integrate valuable insights from [Next.js Weekly](https://nextjsweekly.com/issues) into this starter. +- [ ] 44. Implement type-safe [GraphQL](https://hygraph.com/learn/graphql) support by using [Fuse.js](https://fusejs.org) framework. +- [ ] 45. Implement the best things from [Payload CMS](https://github.com/payloadcms/payload) with Relivator's improvements. +- [ ] 46. Implement Storybook 8.x support (read the "[Storybook for React Server Components](https://storybook.js.org/blog/storybook-react-server-components)" announcement). +- [ ] 47. Implement smart and unified log system, both for development and production, both for console and writing to specific files. +- [ ] 48. Implement Sentry to handle errors and CSP reports for the application. +- [ ] 49. Implement Relivator's/Reliverse's own version of [Saas UI](https://saas-ui.dev) to be fully compatible with our project with only needed functionality, with using Tailwind and Shadcn instead of Chakra. +- [ ] 50. Implement our own fork of [Radix Themes](https://radix-ui.com) library with set up `<main>` as wrapper instead of its current `<section>`; OR implement our very own solution which generates Tailwind instead of Radix's classes. +- [ ] 51. Implement full [Million.js](https://million.dev) support (read [Million 3.0 Announcement](https://million.dev/blog/million-3) to learn more). +- [ ] 52. Implement file uploads using [UploadThing](https://uploadthing.com) and [Cloudinary](https://cloudinary.com). +- [ ] 53. Implement dynamic switching between app features, like database provider, by making corresponding checks for environment variables. +- [ ] 54. Implement docs to the project and move each explanation from the code into that docs. +- [ ] 55. Implement deep feature-parity and easy-migration compatibility with Reliverse. +- [ ] 56. Implement cooperation possibilities by using things like [liveblocks](https://liveblocks.io). +- [ ] 57. Implement CLI to quickly get Relivator with selected options only; try to use [Charm](https://charm.sh) things to build the Reliverse CLI. +- [ ] 58. Implement AI like GPT chat features by using [Vercel AI SDK](https://sdk.vercel.ai/docs) (see: [Introducing the Vercel AI SDK](https://vercel.com/blog/introducing-the-vercel-ai-sdk)). +- [ ] 59. Implement advanced theme switching without flashing, utilizing Tailwind Dark Mode with [React Server Side support](https://michaelangelo.io/blog/darkmode-rsc) and dynamic cookies. +- [ ] 60. Implement [Jest](https://jestjs.io) testing, optimized for Next.js. +- [ ] 61. Guarantee that every possible page is enveloped using predefined shell wrappers. +- [ ] 62. Generously write comment only if it really is needed. Rewrite all code in the way to eliminate need in describing code in comments (read more in "Clean Code" book by Robert Cecil Martin). Consider using `/** block comment */` only in the `.mjs` and `.js` files. +- [ ] 63. Fully develop advanced sign-up and sign-in pages, integrating both social media and classic form methods. +- [ ] 64. Follow the best practices from the articles and videos like "[10 React Antipatterns to Avoid](https://youtube.com/watch?v=b0IZo2Aho9Y)" (check theirs comment section as well). +- [ ] 65. Follow recommendations from [Material Design 3](https://m3.material.io) and other design systems when relevant. +- [ ] 66. Establish, document, and adhere to conventions, such as maintaining a single naming case style for files and variables. +- [ ] 67. Establish a comprehensive i18n, using country and locale codes, and support even more languages. Ensure native speakers verify each language following machine translation. Consider to use the [next-international](https://github.com/QuiiBz/next-international) library. +- [ ] 68. Ensure ultimate type-safety using strict mode in [TypeScript](https://typescriptlang.org) including ["Do's and Don'ts"](https://typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html) recommendations (without using [dangerous type assertions](https://youtube.com/watch?v=K9pMxqb5IAk), and with [optional types correct usage](https://youtube.com/watch?v=qy6IBZggXSQ), by also using `pnpm typestat` — once you run that, [TypeStat](https://github.com/JoshuaKGoldberg/TypeStat) will start auto-fixing TS typings); And also ensure type-safety with typedRoutes, zod, middleware, etc. +- [ ] 69. Ensure the project lacks any unused items, such as packages, libraries, and variables. Also, make sure the project's code adheres to the [Never Nester principles](https://youtube.com/watch?v=CFRhGnuXG-4). Because, as Linus Torvalds once said, *If you need more than 3 levels of indentation, you're screwed anyway, and should fix the program*. +- [ ] 70. Ensure project has full support for [GSAP](https://gsap.com) (GreenSock Animation Platform) library, with convenient ways to use @gsap/react [useGSAP() hook](https://gsap.com/docs/v3/React/tools/useGSAP). +- [ ] 71. Ensure full Next.js Edge support and compatibility. +- [ ] 72. Ensure full [Biome](https://biomejs.dev), [Bun](https://bun.sh), and [Docker](https://docker.com) support and compatibility. +- [ ] 73. Ensure all website languages are grammatically correct and adhere to the latest rules for each language. +- [ ] 74. Ensure all items in the project are sorted in ascending order unless different sorting is required elsewhere. +- [ ] 75. Ensure the project avoids using redundant imports, such as importing everything from React, when it's sufficient to import only the necessary hooks, for example. The project doesn't use things that are automatically handled by the React Compiler (only where it fails), making the code much more readable. Million Lint must work seamlessly with React Compiler. +- [ ] 76. Ensure accessibility for **users**, **developers** (both beginners and experts), **bots** (like [Googlebot](https://developers.google.com/search/docs/crawling-indexing/googlebot) or [PageSpeed Insights Crawler](https://pagespeed.web.dev)), for **everyone**. +- [ ] 77. Enhance `middleware.ts` configuration with multi-middleware implementation. +- [ ] 78. Employ all relevant [TanStack](https://tanstack.com) libraries. +- [ ] 79. Eliminate each disabling in the `eslint.config.js` file, configure config to strict, but to be still beginner-friendly. +- [ ] 80. Elegantly configure `app.ts`, offering a single config to replace all possible others. +- [ ] 81. Develop workflows for both sellers and customers. +- [ ] 82. Develop an even more sophisticated implementation of user subscriptions and the checkout system via Stripe; and also write Jest/Ava tests for Stripe and use `.thing/hooks/stripe_*.json` [webhookthing](https://docs.webhookthing.com) data files for these tests. +- [ ] 83. Develop an advanced storefront featuring products, categories, and subcategories. +- [ ] 84. Develop an advanced 404 Not Found page with full internationalization support. +- [ ] 85. Develop advanced sign-up, sign-in, and restoration using email-password and magic links. +- [ ] 86. Decrease file count by merging similar items, etc. +- [ ] 87. Create the most beginner-friendly and aesthetically pleasing starter possible. +- [ ] 88. Create an advanced notification system, inclusive of toasters, pop-ups, and pages. +- [ ] 89. Create a new landing page with a distinctive design and update components, plus fully redesign all other pages and components. +- [ ] 90. Consider adding Facebook's [StyleX](https://stylexjs.com/blog/introducing-stylex). However, StyleX currently requires setting up Babel/Webpack in the project, which we avoid to maintain full Turbopack support. As a suitable alternative, consider jjenzz's [Tokenami](https://github.com/tokenami/tokenami#readme) or [Panda CSS](https://panda-css.com) by Chakra. Possibly, we can make a choice between them all while bootstrapping the project with Reliverse CLI. These libraries help with avoiding the deprecated [initial idea](https://stylexjs.com/blog/introducing-stylex/#the-origins-of-stylex) for [CSS-in-JS](https://medium.com/dailyjs/what-is-actually-css-in-js-f2f529a2757). Learn more [here](https://github.com/reactwg/react-18/discussions/110) and in [Next.js docs](https://nextjs.org/docs/app/building-the-application/styling/css-in-js). +- [ ] 91. Confirm the project is free from duplicates, like files, components, etc. +- [ ] 92. Conduct useful tests, including possible stress tests, to simulate and assess app performance under high-traffic conditions. +- [ ] 93. Comprehensively configure RSCs and all other new Next.js 13-15 features. Seamlessly move data fetching between both client-side and server-side by using [useSWR](https://twitter.com/shuding_/status/1794461568505352693). +- [ ] 94. Complete the BA11YC (Bleverse Accessibility Convention) checklist; which may relay on the following principle in the future: [DesignPrototype](https://uiprep.com/blog/ultimate-guide-to-prototyping-in-figma)-[CodePrototype](https://medium.com/@joomiguelcunha/the-power-of-prototyping-code-55f4ed485a30)-CodeTests-HqDesign-[TDD](https://en.wikipedia.org/wiki/Test-driven_development)-HqCode-[CI](https://en.wikipedia.org/wiki/CI/CD). +- [ ] 95. Complete parts of the [BA11YC (Bleverse Accessibility Convention) checklist](https://github.com/bs-oss/BA11YC). This includes using software [Design Patterns](https://refactoring.guru/design-patterns/what-is-pattern) for code refactoring. +- [ ] 96. Check all components with side-effects for re-rendering, it is recommended to re-render each component a maximum of 2 times ([good video about it (in Ukrainian)](https://youtu.be/uH9uMH2e5Ts)). +- [ ] 97. Boost app performance scores on platforms like Google PageSpeed Insights. Ensure the app passes all rigorous tests. +- [ ] 98. Apply the [nuqs](https://nuqs.47ng.com) library where appropriate; for older "next-usequerystate" (the old package's name) versions [read the article](https://francoisbest.com/posts/2023/storing-react-state-in-the-url-with-nextjs). +- [ ] 99. All third-party libraries and React components should be appropriately isolated. This includes verifying data from these libraries, such as Clerk, and wrapping the components with the "use client" directive as necessary. +- [ ] 100. Add a reviews section to the landing page. Obtain feedback on Relivator from five widely recognized individuals on the web. +- [ ] 101. Add an admin dashboard that includes stores, products, orders, subscriptions, and payments. +- [ ] 102. Add global color variables to all places where they are applied, instead of having hardcoded colors. +- [ ] 103. Add pop-ups for cookies/GDPR notifications (with a respective management settings page), and Google floating notifications for quick login, etc. +- [ ] 104. Add some interesting and useful types to the project, for example, using the [type-fest](https://github.com/sindresorhus/type-fest) library. +- [ ] 105. Add the integration of a smart git-hooks system with various additional useful functionality. +- [ ] 106. Add the most valuable and useful ESLint things from [awesome-eslint](https://github.com/dustinspecker/awesome-eslint) collection. + +[![Join the Relivator Discord](https://discordapp.com/api/guilds/1075533942096150598/widget.png?style=banner2)][badge-discord] + +## License + +This project is licensed under [the MIT License](https://choosealicense.com/licenses/mit) and is free to use and modify for your own projects. Please visit the [license](LICENSE) file for details. Since this project is under a free license, the author reserves the right to include referral links. The author may receive compensation from these links if users follow them and, for example, pay their first bill. Thank you all for your understanding! + +🌐 <https://relivator.bleverse.com> + +## Changelog + +*a.k.a. What's Happening?!* + +### 1.2.6 - August 4, 2024 – The Resurrection Update + +Below you can see a small copy of [the article from Bleverse Docs](https://docs.bleverse.com/en/blog/relivator/v126), which is possibly outdated. Please refer to [1.2.6 Release Notes Page on GitHub](https://github.com/blefnk/relivator-nextjs-template/releases/tag/1.2.6) or to [this blog post](https://docs.bleverse.com/en/blog/relivator/v126) to read the most recent version. Bleverse Docs also has translations of the article into other languages; and will contain even more information about Relivator than this README.md, including notes from all past and future releases. + +**Relivator is Back with Version 1.2.6!** 🥳 + +We are excited to announce the release of Relivator 1.2.6! This version marks the end of the "all-in-one" approach as we prepare for a more modular future with Reliverse CLI starting from version 1.3.0. The 1.2.6 release includes significant updates, especially in the database logic. The README.md has been significantly updated. Moving forward, we will introduce Canary, Release Candidate (RC), and General Availability (GA) branches for better version management. 1.2.6 will serve as a foundation, helping us transition more smoothly to the release of those 1.3.0's branches. + +### Major Changes and Improvements + +- **Database Updates**: This is the last release that simultaneously supports PostgreSQL/MySQL and NextAuth.js/Clerk integrations. +- **React 19 Preparation**: Work has commenced on upgrading from React 18 to React 19. +- **Updated Libraries**: The project now uses next-auth v5, clerk v5 and optionally supports tailwindcss v4. Refer to the updated README.md for more details. + +### Migration Guidance + +Starting from version 1.3.1, we will provide comprehensive guides for migrating from older versions. The usual migration process involves reviewing commit changes and integrating necessary updates into your custom code. However, due to the extensive changes in versions 1.2.6 and 1.3.0, this method is not feasible. We recommend reinstalling the project and transferring your custom features from the previous version to the new version of starter. Thank you for your understanding! -1. [Introduction to Next.js and React](https://youtube.com/watch?v=h2BcitZPMn4) by [Lee Robinson](https://twitter.com/leeerob) -2. [Relivator: Next.js 14 Starter (Release Announce of Relivator on Medium)](https://cutt.ly/awf6fScS) by [Nazarii Korniienko @Blefnk](https://github.com/blefnk) -3. [Welcome to the Wild World of TypeScript, Mate! Is it scary?](https://cutt.ly/CwjVPUNu) by [Nazarii Korniienko @Blefnk](https://github.com/blefnk) -4. [React: Common Mistakes in 2023](https://docs.google.com/presentation/d/1kuBeSh-yTrL031IlmuwrZ8LvavOGzSbo) by [Cory House](https://twitter.com/housecor) -5. [Thoughts on Next.js 13, Server Actions, Drizzle, Neon, Clerk, and More](https://github.com/Apestein/nextflix/blob/main/README.md#overall-thoughts) by [@Apestein](https://github.com/Apestein) -6. [Huge Next-Multilingual Readme About i18n](https://github.com/Avansai/next-multilingual#readme) by [@Avansai](https://github.com/Avansai) -7. [Applying Design Principles in React](https://blog.openreplay.com/applying-design-principles-in-react) by [Jeremiah (Jerry) Ezekwu](<https://blog.openreplay.com/authors/jeremiah-(jerry)-ezekwu/>) -8. [The Power of Prototyping Code](https://medium.com/@joomiguelcunha/the-power-of-prototyping-code-55f4ed485a30) by [João Miguel Cunha](https://medium.com/@joomiguelcunha) -9. [Software Prototyping](https://en.wikipedia.org/wiki/Software_prototyping) on Wikipedia -10. [TDD: Test-driven development](https://en.wikipedia.org/wiki/Test-driven_development) on Wikipedia -11. [React 18 Discussions](https://github.com/reactwg/react-18/discussions) by [React Working Group](https://github.com/reactwg) +To make the migration as smooth as possible, it's recommended to create a "`cluster`" folder in "`src`" and moving all your custom code there. If needed, you can adjust the paths using the [Find and Replace](https://code.visualstudio.com/docs/editor/codebasics#_search-and-replace) feature in VSCode. This will make it much easier to save and transfer your custom code to Relivator 1.2.6. -_More learning resources can be found within the files of this repository._ +### Default Database Change -## Release Notes +Neon PostgreSQL is now the default database instead of PlanetScale MySQL, as the latter no longer offers a free tier. If you require MySQL, [Railway](https://railway.app?referralCode=sATgpf) offers a more affordable alternative with a $5 credit without requiring a credit card. Note that this version has been primarily tested with Neon PostgreSQL. + +### Security and Code Improvements + +- **Type Safety and Editor Autocomplete**: This update enhances type safety and editor autocomplete for Drizzle ORM libraries. +- **Prettier Replaced by Biome**: Prettier has been removed in favor of Biome. The Prettier config will be removed in the next version from the `addons\terminal\reliverse\relimter\core\temp` folder. You can re-add it by running the `reliverse` command starting from Relivator 1.3.0. + +### Reliverse Scripts Transition + +Reliverse scripts have moved from the "unknown viability" stage to the "unstable" stage. As always, use them at your own risk and make backups. These scripts are now located in the `addons/relimter/[core|python]` folder. Most scripts require Python to be installed. + +For more details on this update, you can read my detailed posts in the Relivator thread on Discord. Start with [this message](https://discord.com/channels/1075533942096150598/1155482425862922370/1241995095125786624). + +### Thank You So Much + +If anyone have any questions or issues, don't hesitate to contact me, means @blefnk, on Discord or GitHub. For more information about 1.2.6 and 1.3.0, please visit `#⭐-relivator` chat on the project's Discord server and the [GitHub Issues](https://github.com/blefnk/relivator-nextjs-template/issues) page. + +Thank you for your continued support and happy coding with [Reliverse Website Builder v0.4.0](https://github.com/blefnk/reliverse-website-builder) and [Relivator Next.js Template v1.2.6](https://github.com/blefnk/relivator-nextjs-template)! + +### Release Notes 1.2.5-1.0.0 + +**This is what happened before 1.2.6 version:** + +<details> + <summary>v1.2.5 — 27.02.2024</summary> + +Hello! I, @blefnk Nazar Kornienko, finally have the opportunity to get back to working on Relivator after a month of exams at university. Thanks to all the new starter users! The project already has over 520 stars, 110 forks, 20 repository watchers, and 45 users in Discord - that's really cool and exciting! + +I also want to thank the active Discord users of the project: *codingisfun, el_ade, righthill, nyquanh, spacecoder315, adelinb*. Thank you to everyone who creates PR/Issues and everyone else who actively uses the starter, whose nicknames I don't know. Your feedback and contributions are incredibly valuable for the development of the project! + +Since there hasn't been an update in over a month, I'm going to make the transition to the next major version smoother. Therefore, version 1.2.5 has been released to simply refresh the dependencies and other minor details and README a bit. This small update will also allow me to check if certain users continue to have the individual strange problems they reported. + +If everything goes well, the next update will already be version 1.3.0. By the way, I'm working on 1.2.x and 1.3.0 in parallel, like in big studios, haha. But please note: some files and lines of code was disabled by default for this version to fix and check some things. By the way, the third digit means that this update is not mandatory, but still recommended. And Relivator 1.3.0 may or may not come with a canary version of React/Next.js to start preparing for the upcoming release of React 19. + +Well, that's all for today, all the best to everyone, and may your `pnpm latest` and `pnpm appts` always be successful! As usual, I try to write a short announcement, but it turns out a few paragraphs, that's how we live! 😄 + +P.S. And, please, don't pay attention that so many files have been "changed" in the latest commit, looks like it's because of Prettier I think, I only updated a few files, and if it's important to someone, please let me know in Discord's DM and I'll list you these files. + +[Read more about v1.2.5](https://github.com/blefnk/relivator-nextjs-template/releases/edit/1.2.5) + +</details> + +<details> + <summary>v1.2.4 — 13.01.2024</summary> + +Just a small hotfix to improve the developer experience. + +[Read more about 1.2.4](https://github.com/blefnk/relivator-nextjs-template/releases/edit/1.2.4) + +</details> <details> - <summary>1.2.3 | 12.01.2024</summary> + <summary>v1.2.3 — 12.01.2024</summary> Just a small hotfix to improve the developer experience. -[Visit release page to learn more...](https://github.com/blefnk/relivator/releases/edit/1.2.3) +[Read more about 1.2.3](https://github.com/blefnk/relivator-nextjs-template/releases/edit/1.2.3) </details> <details> - <summary>1.2.2 | 03.01.2024</summary> + <summary>1.2.2 — 03.01.2024</summary> -1.2.2 brings ESLint Stylistic Plugin into your life. This will make your work with the project even more enjoyable. +1.2.2 brings ESLint Stylistic Plugin into the life. This will make the work with the project even more enjoyable. Remember, Relivator is designed to be beginner-friendly, so quite a lot of ESLint options are turned off, just turn on what you need. These turn-offs will be gradually eliminated as we move towards the massive 2.0.0, which will significantly raise the project's standards, being professional, will be even more convenient for beginners. -[Visit release page to learn more...](https://github.com/blefnk/relivator/releases/edit/1.2.2) +[Read more about v1.2.2](https://github.com/blefnk/relivator-nextjs-template/releases/edit/1.2.2) </details> <details> - <summary>1.2.1 | 02.01.2024</summary> + <summary>1.2.1 — 02.01.2024</summary> -This is quite a small update compared to all the past ones, but this one also deserves your attention. Now, updates will generally be smaller but will appear more frequently. Thanks to this, it will be possible to easily update forks and independent projects that use Relivator as their base. +This is quite a small update compared to all the past ones, but this one also deserves the attention. Now, updates will generally be smaller but will appear more frequently. Thanks to this, it will be possible to easily update forks and independent projects that use Relivator as their base. Update v1.2.1 adds Chinese localization, and among other things, work has begun on the so-called token system, which will allow future versions to work with Figma design systems in an automated way. It will also help to make the styles in the project cleaner by significantly reducing the number of Tailwind classes. For this, Relivator now installs the wonderful package @tokenami, developed by @jjenzz; Jenna, thank you so much for this library! p.s. 1.2.1 is the first commit to the Relivator repository that no longer contains an emoji at the beginning of its name. Thanks to this, contributors to Relivator/Reliverse will no longer have to spend time inventing a suitable emoji. -[Visit release page to learn more...](https://github.com/blefnk/relivator/releases/edit/1.2.1) +[Read more about v1.2.1](https://github.com/blefnk/relivator-nextjs-template/releases/edit/1.2.1) </details> <details> - <summary>1.2.0 | [27.12.2023] 🎄 Relivator v1.2.0 is here! 🥳 Click to see the announcement 🎁</summary> + <summary>1.2.0 — [27.12.2023] 🎄 Relivator v1.2.0 is here! 🥳 Click to see the announcement 🎁</summary> -_Relivator 1.2.0 is already here! I, [@blefnk Nazarii Korniienko](https://github.com/blefnk), am thrilled to wrap up this year 2023, proudly presenting this release to the OSS community! So, the result of over two months of hard work is finally here!_ +*Relivator 1.2.0 is already here! I, [@blefnk Nazar Kornienko](https://github.com/blefnk), am thrilled to wrap up this year 2023, proudly presenting this release to the OSS community! So, the result of over two months of hard work is finally here!* -In this version, significant attention was focused on stability, security, performance optimization, and a substantial improvements in design—both visually, UX, and the logic of how the app works. A lot was really done, too much to list everything! Be sure to install it and check it out for yourself! +In this version, significant attention was focused on stability, security, performance optimization, and a substantial improvements in design—both visually, UX, and the logic of how the app works. A lot was really done, too much to list everything! Be sure to install it and check it out! By the way, you can now enjoy a finely-tuned ESLint Flat Config! Also, it's worth noting that Clerk, since version 1.1.0, is no longer considered deprecated in the Relivator project. Thanks to 1.2.0, Clerk now works seamlessly with an easy switch to NextAuth.js when needed, all on the fly. Plus, full support for Turbopack (next dev --turbo) is finally here, even for next-intl! @@ -458,11 +1019,11 @@ As for next-intl, finally, we can now enjoy internationalization that works not Many unique solutions have been implemented in this new version. Moreover, using Relivator from this version, you have the opportunity to try out the alpha version of our unique Code-First/No-Code Builder system for React pages and components (which will appear in Reliverse CMS in the future). Just visit the Admin page while in the development environment and enjoy. -If you have already used Relivator before, please pay attention, this is very important! Be sure to check the updated .env.example file and update your .env file accordingly. +If you have already used Relivator before, please pay attention, this is very important! Be sure to check the updated .env.example file and update the .env file accordingly. -As a small teaser/spoiler, for Relivator 1.3.0, even more improvements in visual design and UX are planned; 1.4.0 will come with a magical CLI implementation, allowing you to quickly obtain only the necessary features and dependencies for your app (even automated updates and the ability to add other functions and packages to an already installed app); 1.5.0 will undergo a full code refactoring that will meet all the best standards and practices; 1.6.0-2.0.0+ versions, apart from many other things, will receive most of the items currently unchecked in the Roadmap (located in the project's README.md). It's going to be incredible! +As a small teaser/spoiler, for Relivator 1.3.0, even more improvements in visual design and UX are planned; 1.4.0 will come with a magical CLI implementation, allowing you to quickly obtain only the necessary features and dependencies for the app (even automated updates and the ability to add other functions and packages to an already installed app); 1.5.0 will undergo a full code refactoring that will meet all the best standards and practices; 1.6.0-2.0.0+ versions, apart from many other things, will receive most of the items currently unchecked in the Roadmap (located in the project's README.md). It's going to be incredible! -So, install this new version of Relivator 1.2.0 and appreciate the multitude of incredible features, and freely use it in your own projects today. Please use your preferred feedback channels to share your thoughts on Relivator 1.2.0 and what you would like to see in future releases. +So, install this new version of Relivator 1.2.0 and appreciate the multitude of incredible features, and freely use it in the own projects today. Please use the preferred feedback channels to share the thoughts on Relivator 1.2.0 and what you would like to see in future releases. Don't forget to also check out the significantly updated README.md, it's worth it. @@ -471,7 +1032,7 @@ Enjoy! ❄️☃️ Merry Christmas and Happy New Year 2024! 🎇🥳 </details> <details> - <summary>1.1.0 | 🔥 The Most Feature-Rich Next.js 14 Starter</summary> + <summary>1.1.0 — 🔥 The Most Feature-Rich Next.js 15 Starter</summary> Here it is! Relivator has been updated to version 1.1.0! @@ -481,75 +1042,54 @@ Ever dreamed of having both MySQL/PostgreSQL and Clerk/NextAuth.js in one projec Among many other new and fixed things, Stripe is now fully functional and comes with extensive docs in the form of comments within the relevant files. -`Please star this repository` to show your support! Thank you to everyone who has shown interest in this project! +`Please star this repository` to show the support! Thank you to everyone who has shown interest in this project! -Please check out the updated list of project features in the project's README. Enjoy and please share your feedback! +Please check out the updated list of project features in the project's README. Enjoy and please share the feedback! -[Visit release page to learn more...](https://github.com/blefnk/relivator/releases/edit/1.1.0) +[Read more about v1.1.0](https://github.com/blefnk/relivator-nextjs-template/releases/edit/1.1.0) </details> <details> - <summary>1.0.0 | 🎉 Relivator Release</summary> + <summary>1.0.0 — 🎉 Relivator Release</summary> -How to Install and Get Started? Please visit [the project's README](https://github.com/blefnk/relivator#readme), where you can always find up-to-date information about the project and how to install it easily. +How to Install and Get Started? Please refer to the [🏗️ Installation](#installation) section, where you can always find information about the project and how to install it easily. -[Visit release page to learn more...](https://github.com/blefnk/relivator/releases/edit/1.0.0) +[Read more about v1.0.0](https://github.com/blefnk/relivator-nextjs-template/releases/tag/1.0.0) </details> -## Migration from Other Starters to Relivator - -If you've been exploring which Next.js starter to select for your next project like [create-next-app](https://vercel.com/templates/next.js/nextjs-boilerplate), [create-t3-app](https://create.t3.gg), [Next.js Commerce (Vercel Store)](https://vercel.store), [Skateshop](https://github.com/sadmann7/skateshop), [OneStopShop](https://github.com/jackblatch/OneStopShop), [Taxonomy](https://github.com/shadcn-ui/taxonomy)/[nextflix](https://github.com/Apestein/nextflix), [payload](https://github.com/payloadcms/payload), [Medusa](https://github.com/medusajs/medusa), or [any other projects](https://github.com/blefnk/relivator/wiki/Project-Credits-&-Contributors) – your search can end here. - -_«There are a lot of impractical things about owning a ~~Porsche~~ Relivator. But they're all offset by the driving experience. It really is unique. ~~Lamborghinis~~ Skateshop and ~~Ferraris~~ Vercel Store come close. And they are more powerful in specific cases, but they don't handle like a ~~Porsche~~ Relivator.» © Kevin O'Leary_ - -All these projects are incredible, and if minimalism appeals to you, we recommend checking them out. The creators behind these projects are extremely talented individuals, and we offer them our endless thanks. Without them, this starter would not exist. - -However, **if you want a POWERHOUSE**—Relivator suitable for every scenario—then **Relivator is definitely the starter you need** to fork it right now! Relivator incorporates numerous features from all those projects and strives to push the limits of Next.js and React capabilities. With Relivator, your opportunities are boundless. - -If you **choose Relivator to be your next project starter** and you want to migrate from the projects above to Relivator, then please give us a few days. We will use the project wiki to write there guide how to do this. In this guide you will learn how to migrate your project to our project. Migration guide will be available for both "app" and "pages" directories. - -## Contributing and Credits - -_This section will be enhanced soon with simpler steps to get everything ready._ - -Contributions are warmly welcomed! We express our gratitude to everyone who has contributed to this repository. Your contributions will be recognized. If you have any questions or suggestions, please open an issue. For more information, see the [contributing guide](https://github.com/blefnk/relivator/blob/main/contributing.md). +Please visit the [CHANGELOG.md](.github/CHANGELOG.md) or [Bleverse Docs](https://docs.bleverse.com/en/blog/relivator/changelog) to read the release notes for older versions. -Please visit [this special wiki page](https://github.com/blefnk/relivator/wiki/Project-Credits-&-Contributors) to view the full list of credits and contributors. To contribute to Bleverse Relivator, follow these steps: +## The Final Words -1. Begin by reading the "How to Install and Get Started" section on the top of this repository, and by reading [CONTRIBUTING.md](https://github.com/blefnk/relivator/blob/main/contributing.md) page. -2. Create a branch: `git checkout -b <branch_name>`. -3. Make and commit your changes: `git commit -m '<commit_message>'` -4. Push to the original branch: `git push origin <branch_name>` -5. Submit the pull request. - -Alternatively, check the GitHub docs on [how to create a pull request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request). - -## Project License +This Next.js 15 starter — Relivator — was crafted with love by [@blefnk Nazar Kornienko](https://github.com/blefnk) and the incredible [Bleverse OSS community](https://github.com/blefnk/relivator-nextjs-template/wiki/Project-Credits-&-Contributors). We are deeply grateful for all your contributions and support for this project. -This project is licensed under MIT and is free to use and modify for your own projects. Check the [LICENSE.md](https://github.com/blefnk/relivator/LICENSE.md) file for details. +Happy coding! Embark on the coding adventure, learn, iterate, and most importantly – enjoy the process! Remember – this is a space of learning and experimentation. Dive in and savor the journey! 🚀🌌 -🌐 [https://relivator.bleverse.com](https://relivator.bleverse.com) +Check out [our other free Next.js 15 starter](https://github.com/blefnk/reliverse). This monorepo provides the tech used in the current starter and adds: Turborepo/Turbopack, Prisma, Valibot, Lucia, Clerk, and much more, as we experimentally attempt to combine all vital and widely-used tech. Think of it as: Reliverse (WordPress) + Relivator (WooCommerce) = 😍. So, start right now! Start today with Relivator! ---- +> 🚀 **Ready to launch?** Start building your project with Relivator and Reliverse as soon as possible! With one-click deploy on Vercel: -**Follow Us Everywhere:** [GitHub](https://github.com/blefnk) | [Twitter/𝕏](https://x.com/blefnk) | [Discord](https://discord.gg/Pb8uKbwpsJ) | [Fiverr](https://fiverr.com/blefnk) | [LinkedIn](https://linkedin.com/in/blefnk) | [Facebook](https://facebook.com/blefnk) +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fblefnk%2Frelivator-nextjs-template\&project-name=relivator\&repository-name=relivator) -This Next.js 14 starter—Relivator—was crafted with love by [@blefnk Nazarii Korniienko](https://github.com/blefnk), and by the incredible [Bleverse OSS community](https://github.com/blefnk/relivator/wiki/Project-Credits-&-Contributors). We are deeply grateful for all the contributions and support provided by everyone for this project. +> 💻 **Prefer manual installation?** Refer to the [🏗️ Installation](#installation) section or just clone the project using the most classical way: -_«I couldn't find the ~~sports car~~ Next.js starter of my dreams, so I built it myself.» © Ferdinand Porsche_ +```bash +git clone https://github.com/blefnk/relivator.git +``` ---- +[2023-2024 © Nazar Kornienko / Nazar Kornienko / blefnk](https://github.com/blefnk) (<https://relivator.bleverse.com>) -Happy coding! Embark on your coding adventure, learn, iterate, and most importantly – enjoy the process! Remember – this is a space of learning and experimentation. Dive in and savor the journey! 🚀🌌 +![Relivator OG Image](public/og.png) -![Bleverse Relivator OG Image](/public/og-image.png) +[![Join the Relivator Discord](https://discordapp.com/api/guilds/1075533942096150598/widget.png?style=banner2)][badge-discord] -Check out [our other free Next.js 14 starter](https://github.com/blefnk/reliverse). This one, a monorepo, provides the tech used in the current starter and adds: Turborepo/Turbopack, Prisma, Valibot, Lucia, Clerk, and much more, as we experimentally attempt to combine all vital and widely-used tech. It's like thinking about: Reliverse (WordPress) + Relivator (WooCommerce) = 😍. So, start right now! Start today. With Relivator! +[🌐 Demo](https://relivator.bleverse.com) | [👋 Introduction](#introduction) | [🏗️ Installation](#installation) | [🤔 FAQ](#faq) | [🔍 Details](#details) | [✅ Roadmap](#roadmap) | [📖 Changelog](#changelog) -```bash -git clone https://github.com/blefnk/relivator.git -``` +**Follow Us Everywhere:** [𝕏](https://x.com/blefnk) | [GitHub](https://github.com/blefnk) | [LinkedIn](https://linkedin.com/in/blefnk) | [Facebook](https://facebook.com/blefnk) | [Discord](https://discord.gg/Pb8uKbwpsJ) | [Fiverr](https://fiverr.com/blefnk) -[bleverse-discord]: https://discord.gg/Pb8uKbwpsJ +[badge-discord]: https://badgen.net/discord/members/Pb8uKbwpsJ?icon=discord&label=discord&color=purple +[badge-npm]: https://badgen.net/npm/v/reliverse?icon=npm&color=green&label=%40blefnk%2Freliverse +[link-discord]: https://discord.gg/Pb8uKbwpsJ +[link-npm]: https://npmjs.com/package/reliverse/v/latest diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 15c94f0c..00000000 --- a/SECURITY.md +++ /dev/null @@ -1,26 +0,0 @@ -# Security Policy - -## Supported Versions - -Our commitment to security extends to the following version of the project: - -| Version | Supported | -| ------- | ------------------ | -| @latest | :white_check_mark: | - -## Reporting a Vulnerability - -We greatly value the security community's efforts in helping keep our project safe. If you've discovered a security vulnerability, your responsible disclosure is crucial for us. Here's how you can report it: - -1. **Contact Method**: Email us at [blefnk@gmail.com](mailto:blefnk@gmail.com). -2. **Email Subject**: Please use a concise yet descriptive subject, such as "Security Vulnerability Found". -3. **Vulnerability Details**: Provide a comprehensive description of the vulnerability. Include reproduction steps, and any other information that might help us understand and resolve the issue effectively. -4. **Proof of Concept**: Attach any proof-of-concept or sample code if available. Please ensure that your research does not involve destructive testing or violate any laws. -5. **Encryption**: For secure communication, use our public PGP key available on our website or public key servers. -6. **Response Timeline**: We aim to acknowledge your report within [e.g., 48 hours] and will keep you updated on our progress. -7. **Investigation and Remediation**: Our team will promptly investigate and work on resolving the issue. We'll maintain communication with you throughout this process. -8. **Disclosure Policy**: Please refrain from public disclosure until we have mitigated the vulnerability. We will collaborate with you to decide on an appropriate disclosure timeline, considering the issue's severity. - -We're grateful for your contributions to our project's security. Contributors who help improve our security may be publicly acknowledged (with consent). - -_Note: Our security policy may be updated periodically._ diff --git a/addons/browser/reliverse/ui/Accordion.tsx b/addons/browser/reliverse/ui/Accordion.tsx new file mode 100644 index 00000000..ab934a69 --- /dev/null +++ b/addons/browser/reliverse/ui/Accordion.tsx @@ -0,0 +1,75 @@ +"use client"; + +import type { ComponentPropsWithoutRef, ComponentRef } from "react"; +import { forwardRef } from "react"; + +import * as AccordionPrimitive from "@radix-ui/react-accordion"; +import { ChevronDown } from "lucide-react"; + +import { cn } from "~/utils"; + +const Accordion = AccordionPrimitive.Root; + +const AccordionItem = forwardRef< + ComponentRef<typeof AccordionPrimitive.Item>, + ComponentPropsWithoutRef<typeof AccordionPrimitive.Item> +>(({ className, ...props }, ref) => ( + <AccordionPrimitive.Item + className={cn("border-b", className)} + ref={ref} + {...props} + /> +)); + +AccordionItem.displayName = "AccordionItem"; + +const AccordionTrigger = forwardRef< + ComponentRef<typeof AccordionPrimitive.Trigger>, + ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger> +>(({ children, className, ...props }, ref) => ( + <AccordionPrimitive.Header className="flex"> + <AccordionPrimitive.Trigger + className={cn( + ` + flex flex-1 items-center justify-between py-4 font-medium + transition-all + + [&[data-state=open]>svg]:rotate-180 + + hover:underline + `, + className, + )} + ref={ref} + {...props} + > + {children} + <ChevronDown className="size-4 shrink-0 transition-transform duration-200" /> + </AccordionPrimitive.Trigger> + </AccordionPrimitive.Header> +)); + +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; + +const AccordionContent = forwardRef< + ComponentRef<typeof AccordionPrimitive.Content>, + ComponentPropsWithoutRef<typeof AccordionPrimitive.Content> +>(({ children, className, ...props }, ref) => ( + <AccordionPrimitive.Content + className={` + overflow-hidden text-sm transition-all + + data-[state=closed]:animate-accordion-up + + data-[state=open]:animate-accordion-down + `} + ref={ref} + {...props} + > + <div className={cn("pb-4 pt-0", className)}>{children}</div> + </AccordionPrimitive.Content> +)); + +AccordionContent.displayName = AccordionPrimitive.Content.displayName; + +export { Accordion, AccordionContent, AccordionItem, AccordionTrigger }; diff --git a/addons/browser/reliverse/ui/Alert-Dialog.tsx b/addons/browser/reliverse/ui/Alert-Dialog.tsx new file mode 100644 index 00000000..fcef1e57 --- /dev/null +++ b/addons/browser/reliverse/ui/Alert-Dialog.tsx @@ -0,0 +1,195 @@ +"use client"; + +import type { + ComponentPropsWithoutRef, + ComponentRef, + HTMLAttributes, +} from "react"; +import { forwardRef } from "react"; + +import { buttonVariants } from "@/browser/reliverse/ui/Button"; +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; + +import { cn } from "~/utils"; + +const AlertDialog = AlertDialogPrimitive.Root; + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger; + +const AlertDialogPortal = ({ + ...props +}: AlertDialogPrimitive.AlertDialogPortalProps) => ( + <AlertDialogPrimitive.Portal {...props} /> +); + +AlertDialogPortal.displayName = AlertDialogPrimitive.Portal.displayName; + +const AlertDialogOverlay = forwardRef< + ComponentRef<typeof AlertDialogPrimitive.Overlay>, + ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay> +>(({ className, ...props }, ref) => ( + <AlertDialogPrimitive.Overlay + className={cn( + ` + fixed inset-0 z-50 bg-background/80 backdrop-blur-sm + + data-[state=closed]:animate-out data-[state=closed]:fade-out-0 + + data-[state=open]:animate-in data-[state=open]:fade-in-0 + `, + className, + )} + {...props} + ref={ref} + /> +)); + +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; + +const AlertDialogContent = forwardRef< + ComponentRef<typeof AlertDialogPrimitive.Content>, + ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content> +>(({ className, ...props }, ref) => ( + <AlertDialogPortal> + <AlertDialogOverlay /> + <AlertDialogPrimitive.Content + className={cn( + ` + fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 + -translate-y-1/2 gap-4 border bg-background p-6 shadow-lg duration-200 + + data-[state=closed]:animate-out data-[state=closed]:fade-out-0 + data-[state=closed]:zoom-out-95 + data-[state=closed]:slide-out-to-left-1/2 + data-[state=closed]:slide-out-to-top-[48%] + + data-[state=open]:animate-in data-[state=open]:fade-in-0 + data-[state=open]:zoom-in-95 data-[state=open]:slide-in-from-left-1/2 + data-[state=open]:slide-in-from-top-[48%] + + md:w-full + + sm:rounded-lg + `, + className, + )} + ref={ref} + {...props} + /> + </AlertDialogPortal> +)); + +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName; + +const AlertDialogHeader = ({ + className, + ...props +}: HTMLAttributes<HTMLDivElement>) => ( + <div + className={cn( + ` + flex flex-col space-y-2 text-center + + sm:text-left + `, + className, + )} + {...props} + /> +); + +AlertDialogHeader.displayName = "AlertDialogHeader"; + +const AlertDialogFooter = ({ + className, + ...props +}: HTMLAttributes<HTMLDivElement>) => ( + <div + className={cn( + ` + flex flex-col-reverse + + sm:flex-row sm:justify-end sm:space-x-2 + `, + className, + )} + {...props} + /> +); + +AlertDialogFooter.displayName = "AlertDialogFooter"; + +const AlertDialogTitle = forwardRef< + ComponentRef<typeof AlertDialogPrimitive.Title>, + ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title> +>(({ className, ...props }, ref) => ( + <AlertDialogPrimitive.Title + className={cn("text-lg font-semibold", className)} + ref={ref} + {...props} + /> +)); + +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName; + +const AlertDialogDescription = forwardRef< + ComponentRef<typeof AlertDialogPrimitive.Description>, + ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description> +>(({ className, ...props }, ref) => ( + <AlertDialogPrimitive.Description + className={cn("text-sm text-muted-foreground", className)} + ref={ref} + {...props} + /> +)); + +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName; + +const AlertDialogAction = forwardRef< + ComponentRef<typeof AlertDialogPrimitive.Action>, + ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action> +>(({ className, ...props }, ref) => ( + <AlertDialogPrimitive.Action + className={cn(buttonVariants(), className)} + ref={ref} + {...props} + /> +)); + +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName; + +const AlertDialogCancel = forwardRef< + ComponentRef<typeof AlertDialogPrimitive.Cancel>, + ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel> +>(({ className, ...props }, ref) => ( + <AlertDialogPrimitive.Cancel + className={cn( + buttonVariants({ + variant: "outline", + }), + ` + mt-2 + + sm:mt-0 + `, + className, + )} + ref={ref} + {...props} + /> +)); + +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName; + +export { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +}; diff --git a/addons/browser/reliverse/ui/Alert.tsx b/addons/browser/reliverse/ui/Alert.tsx new file mode 100644 index 00000000..7fc5ce31 --- /dev/null +++ b/addons/browser/reliverse/ui/Alert.tsx @@ -0,0 +1,92 @@ +import type { HTMLAttributes } from "react"; +import { forwardRef } from "react"; + +import type { VariantProps } from "class-variance-authority"; + +import { cva } from "class-variance-authority"; + +import { cn } from "~/utils"; + +const alertVariants = cva( + ` + relative w-full rounded-lg border px-4 py-3 text-sm + + [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground + + [&>svg+div]:translate-y-[-3px] + + [&>svg~*]:pl-7 + `, + { + defaultVariants: { + variant: "default", + }, + variants: { + variant: { + default: "bg-background text-foreground", + destructive: ` + border-destructive/50 text-destructive + + [&>svg]:text-destructive + + dark:border-destructive + `, + }, + }, + }, +); + +const Alert = forwardRef< + HTMLDivElement, + HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants> +>(({ className, variant, ...props }, ref) => ( + <div + className={cn( + alertVariants({ + variant, + }), + className, + )} + ref={ref} + role="alert" + {...props} + /> +)); + +Alert.displayName = "Alert"; + +const AlertTitle = forwardRef< + HTMLParagraphElement, + HTMLAttributes<HTMLHeadingElement> +>(({ className, ...props }, ref) => ( + // eslint-disable-next-line jsx-a11y/heading-has-content + <h5 + className={cn("mb-1 font-medium leading-none tracking-tight", className)} + ref={ref} + {...props} + /> +)); + +AlertTitle.displayName = "AlertTitle"; + +const AlertDescription = forwardRef< + HTMLParagraphElement, + HTMLAttributes<HTMLParagraphElement> +>(({ className, ...props }, ref) => ( + <div + className={cn( + ` + text-sm + + [&_p]:leading-relaxed + `, + className, + )} + ref={ref} + {...props} + /> +)); + +AlertDescription.displayName = "AlertDescription"; + +export { Alert, AlertDescription, AlertTitle }; diff --git a/src/islands/primitives/aspect-ratio.tsx b/addons/browser/reliverse/ui/Aspect-Ratio.tsx similarity index 100% rename from src/islands/primitives/aspect-ratio.tsx rename to addons/browser/reliverse/ui/Aspect-Ratio.tsx diff --git a/addons/browser/reliverse/ui/Avatar.tsx b/addons/browser/reliverse/ui/Avatar.tsx new file mode 100644 index 00000000..01df3883 --- /dev/null +++ b/addons/browser/reliverse/ui/Avatar.tsx @@ -0,0 +1,70 @@ +"use client"; + +import type { ComponentPropsWithoutRef, ComponentRef, Ref } from "react"; +import { forwardRef } from "react"; + +import * as AvatarPrimitive from "@radix-ui/react-avatar"; + +import { cn } from "~/utils"; + +type AvatarProps = { + className?: string; + ref?: Ref<ComponentRef<typeof AvatarPrimitive.Root>>; +} & ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>; + +const Avatar = forwardRef< + ComponentRef<typeof AvatarPrimitive.Root>, + AvatarProps +>(({ className, ...props }, ref) => ( + <AvatarPrimitive.Root + className={cn( + "relative flex size-10 shrink-0 overflow-hidden rounded-full", + className, + )} + ref={ref} + {...props} + /> +)); + +Avatar.displayName = AvatarPrimitive.Root.displayName; + +type AvatarImageProps = { + className?: string; + ref?: Ref<ComponentRef<typeof AvatarPrimitive.Image>>; +} & ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>; + +const AvatarImage = forwardRef< + ComponentRef<typeof AvatarPrimitive.Image>, + AvatarImageProps +>(({ className, ...props }, ref) => ( + <AvatarPrimitive.Image + className={cn("aspect-square size-full", className)} + ref={ref} + {...props} + /> +)); + +AvatarImage.displayName = AvatarPrimitive.Image.displayName; + +type AvatarFallbackProps = { + className?: string; + ref?: Ref<ComponentRef<typeof AvatarPrimitive.Fallback>>; +} & ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>; + +const AvatarFallback = forwardRef< + ComponentRef<typeof AvatarPrimitive.Fallback>, + AvatarFallbackProps +>(({ className, ...props }, ref) => ( + <AvatarPrimitive.Fallback + className={cn( + "flex size-full items-center justify-center rounded-full bg-muted", + className, + )} + ref={ref} + {...props} + /> +)); + +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; + +export { Avatar, AvatarFallback, AvatarImage }; diff --git a/addons/browser/reliverse/ui/Badge.tsx b/addons/browser/reliverse/ui/Badge.tsx new file mode 100644 index 00000000..0fb9ef3b --- /dev/null +++ b/addons/browser/reliverse/ui/Badge.tsx @@ -0,0 +1,60 @@ +import type { HTMLAttributes } from "react"; + +import type { VariantProps } from "class-variance-authority"; + +import { cva } from "class-variance-authority"; + +import { cn } from "~/utils"; + +const badgeVariants = cva( + ` + inline-flex items-center rounded-lg border px-2.5 py-0.5 text-xs + font-semibold transition-colors + + focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 + `, + { + defaultVariants: { + variant: "default", + }, + variants: { + variant: { + default: ` + border-transparent bg-primary text-primary-foreground shadow + + hover:bg-primary/80 + `, + destructive: ` + border-transparent bg-destructive text-destructive-foreground shadow + + hover:bg-destructive/80 + `, + outline: "text-foreground", + secondary: ` + border-transparent bg-secondary text-secondary-foreground + + hover:bg-secondary/80 + `, + }, + }, + }, +); + +type BadgeProps = {} & HTMLAttributes<HTMLDivElement> & + VariantProps<typeof badgeVariants>; + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( + <div + className={cn( + badgeVariants({ + variant, + }), + className, + )} + {...props} + /> + ); +} + +export { Badge, badgeVariants }; diff --git a/addons/browser/reliverse/ui/Button.tsx b/addons/browser/reliverse/ui/Button.tsx new file mode 100644 index 00000000..ce007191 --- /dev/null +++ b/addons/browser/reliverse/ui/Button.tsx @@ -0,0 +1,85 @@ +import type { ButtonHTMLAttributes } from "react"; +import { forwardRef } from "react"; + +import type { VariantProps } from "class-variance-authority"; + +import { cn } from "@/browser/shared/utils"; +import { Slot } from "@radix-ui/react-slot"; +import { cva } from "class-variance-authority"; + +const buttonVariants = cva( + ` + inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm + font-medium ring-offset-background transition-colors + + disabled:pointer-events-none disabled:opacity-50 + + focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring + focus-visible:ring-offset-2 + `, + { + defaultVariants: { + size: "default", + variant: "default", + }, + variants: { + size: { + default: "h-10 px-4 py-2", + icon: "h-10 w-10", + lg: "h-11 rounded-md px-8", + sm: "h-9 rounded-md px-3", + }, + variant: { + default: ` + bg-primary text-primary-foreground + + hover:bg-primary/90 + `, + destructive: ` + bg-destructive text-destructive-foreground + + hover:bg-destructive/90 + `, + ghost: "hover:bg-accent hover:text-accent-foreground", + link: ` + text-primary underline-offset-4 + + hover:underline + `, + outline: ` + border border-input bg-background + + hover:bg-accent hover:text-accent-foreground + `, + secondary: ` + bg-secondary text-secondary-foreground + + hover:bg-secondary/80 + `, + }, + }, + }, +); + +export type ButtonProps = { + asChild?: boolean; +} & ButtonHTMLAttributes<HTMLButtonElement> & + VariantProps<typeof buttonVariants>; + +const Button = forwardRef<HTMLButtonElement, ButtonProps>( + ({ asChild = false, className, size, variant, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + + return ( + <Comp + className={cn(buttonVariants({ className, size, variant }))} + ref={ref} + {...props} + /> + ); + }, +); + +Button.displayName = "Button"; + +export { Button, buttonVariants }; diff --git a/addons/browser/reliverse/ui/Calendar.tsx b/addons/browser/reliverse/ui/Calendar.tsx new file mode 100644 index 00000000..5b77c394 --- /dev/null +++ b/addons/browser/reliverse/ui/Calendar.tsx @@ -0,0 +1,107 @@ +import { buttonVariants } from "@/browser/reliverse/ui/Button"; + +("use client"); + +import type { ComponentProps } from "react"; +import { DayPicker } from "react-day-picker"; + +import { ChevronLeft, ChevronRight } from "lucide-react"; + +import { cn } from "~/utils"; + +export type CalendarProps = ComponentProps<typeof DayPicker>; + +function Calendar({ + className, + classNames, + showOutsideDays = true, + ...props +}: CalendarProps) { + return ( + <DayPicker + className={cn("p-3", className)} + classNames={{ + caption: "flex justify-center pt-1 relative items-center", + caption_label: "text-sm font-medium", + cell: cn( + ` + relative p-0 text-center text-sm + + [&:has([aria-selected])]:bg-accent + + focus-within:relative focus-within:z-20 + `, + props.mode === "range" + ? ` + [&:has(>.day-range-end)]:rounded-r-md + [&:has(>.day-range-start)]:rounded-l-md + + first:[&:has([aria-selected])]:rounded-l-md + + last:[&:has([aria-selected])]:rounded-r-md + ` + : "[&:has([aria-selected])]:rounded-lg", + ), + day: cn( + buttonVariants({ + variant: "ghost", + }), + ` + size-8 p-0 font-normal + + aria-selected:opacity-100 + `, + ), + day_disabled: "text-muted-foreground opacity-50", + day_hidden: "invisible", + day_outside: "text-muted-foreground opacity-50", + day_range_end: "day-range-end", + day_range_middle: + "aria-selected:bg-accent aria-selected:text-accent-foreground", + day_range_start: "day-range-start", + day_selected: `bg-primary text-primary-foreground hover:bg-primary + hover:text-primary-foreground focus:bg-primary + focus:text-primary-foreground`, + day_today: "bg-accent text-accent-foreground", + head_cell: + "text-muted-foreground rounded-lg w-8 font-normal text-[0.8rem]", + head_row: "flex", + month: "space-y-4", + months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0", + nav: "space-x-1 flex items-center", + nav_button: cn( + buttonVariants({ + variant: "outline", + }), + ` + size-7 bg-transparent p-0 opacity-50 + + hover:opacity-100 + `, + ), + nav_button_next: "absolute right-1", + nav_button_previous: "absolute left-1", + row: "flex w-full mt-2", + table: "w-full border-collapse space-y-1", + ...classNames, + }} + components={{ + // @ts-expect-error TODO: fix + // eslint-disable-next-line @eslint-react/no-nested-components + IconLeft: ({ ...props_ }) => ( + <ChevronLeft className="size-4" {...props_} /> + ), + // eslint-disable-next-line @eslint-react/no-nested-components + IconRight: ({ ...props_ }) => ( + <ChevronRight className="size-4" {...props_} /> + ), + }} + showOutsideDays={showOutsideDays} + {...props} + /> + ); +} + +Calendar.displayName = "Calendar"; + +export { Calendar }; diff --git a/addons/browser/reliverse/ui/CardUI.tsx b/addons/browser/reliverse/ui/CardUI.tsx new file mode 100644 index 00000000..d8162c96 --- /dev/null +++ b/addons/browser/reliverse/ui/CardUI.tsx @@ -0,0 +1,92 @@ +import type { HTMLAttributes, HtmlHTMLAttributes } from "react"; +import { forwardRef } from "react"; + +import { cn } from "@/browser/shared/utils"; + +const Card = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>( + ({ className, ...props }, ref) => ( + <div + className={cn( + "rounded-lg border bg-card text-card-foreground shadow-sm", + className, + )} + ref={ref} + {...props} + /> + ), +); + +Card.displayName = "Card"; + +const CardHeader = forwardRef< + HTMLDivElement, + HtmlHTMLAttributes<HTMLDivElement> +>(({ className, ...props }, ref) => ( + <div + className={cn("flex flex-col space-y-1.5 p-6", className)} + ref={ref} + {...props} + /> +)); + +CardHeader.displayName = "CardHeader"; + +const CardTitle = forwardRef< + HTMLParagraphElement, + HTMLAttributes<HTMLHeadingElement> +>(({ children, className, ...props }, ref) => ( + <h3 + className={cn( + "text-2xl font-semibold leading-none tracking-tight", + className, + )} + ref={ref} + {...props} + > + {children} + </h3> +)); + +CardTitle.displayName = "CardTitle"; + +const CardDescription = forwardRef< + HTMLParagraphElement, + HTMLAttributes<HTMLParagraphElement> +>(({ className, ...props }, ref) => ( + <p + className={cn("text-sm text-muted-foreground", className)} + ref={ref} + {...props} + /> +)); + +CardDescription.displayName = "CardDescription"; + +const CardContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>( + ({ className, ...props }, ref) => ( + <div className={cn("p-6 pt-0", className)} ref={ref} {...props} /> + ), +); + +CardContent.displayName = "CardContent"; + +const CardFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>( + ({ className, ...props }, ref) => ( + <div + className={cn("flex items-center p-6 pt-0", className)} + ref={ref} + {...props} + /> + ), +); + +CardFooter.displayName = "CardFooter"; + +export { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +}; diff --git a/addons/browser/reliverse/ui/Carousel.tsx b/addons/browser/reliverse/ui/Carousel.tsx new file mode 100644 index 00000000..0a0aad8d --- /dev/null +++ b/addons/browser/reliverse/ui/Carousel.tsx @@ -0,0 +1,281 @@ +"use client"; + +import type { ComponentProps, HTMLAttributes, KeyboardEvent } from "react"; +import { + createContext, + forwardRef, + use, + useCallback, + useEffect, + useState, +} from "react"; + +import type { UseEmblaCarouselType } from "embla-carousel-react"; + +import { Button } from "@/browser/reliverse/ui/Button"; +import { ArrowLeftIcon, ArrowRightIcon } from "@radix-ui/react-icons"; +import useEmblaCarousel from "embla-carousel-react"; + +import { cn } from "~/utils"; + +type CarouselApi = UseEmblaCarouselType["1"]; + +type UseCarouselParameters = Parameters<typeof useEmblaCarousel>; + +type CarouselOptions = UseCarouselParameters["0"]; + +type CarouselPlugins = UseCarouselParameters["1"]; + +type CarouselProps = { + opts?: CarouselOptions; + orientation?: "horizontal" | "vertical"; + plugins?: CarouselPlugins; + setApi?: (api: CarouselApi) => void; +}; + +type CarouselContextProps = { + api: ReturnType<typeof useEmblaCarousel>[1]; + canScrollNext: boolean; + canScrollPrev: boolean; + carouselRef: ReturnType<typeof useEmblaCarousel>[0]; + scrollNext: () => void; + scrollPrev: () => void; +} & CarouselProps; + +const CarouselContext = createContext<CarouselContextProps | null>(null); + +function useCarousel() { + const context = use(CarouselContext); + + if (!context) { + throw new Error("useCarousel must be used within a <Carousel />"); + } + + return context; +} + +const Carousel = forwardRef< + HTMLDivElement, + CarouselProps & HTMLAttributes<HTMLDivElement> +>( + ( + { + children, + className, + opts, + orientation = "horizontal", + plugins, + setApi, + ...props + }, + ref, + ) => { + const [carouselRef, api] = useEmblaCarousel( + { + ...opts, + axis: orientation === "horizontal" ? "x" : "y", + }, + plugins, + ); + + const [canScrollPrevious, setCanScrollPrevious] = useState(false); + const [canScrollNext, setCanScrollNext] = useState(false); + + const onSelect = useCallback((api: CarouselApi) => { + if (!api) { + return; + } + + setCanScrollPrevious(api.canScrollPrev()); + setCanScrollNext(api.canScrollNext()); + }, []); + + const scrollPrevious = useCallback(() => { + api && api.scrollPrev(); + }, [api]); + + const scrollNext = useCallback(() => { + api && api.scrollNext(); + }, [api]); + + const handleKeyDown = useCallback( + (event: KeyboardEvent<HTMLDivElement>) => { + if (event.key === "ArrowLeft") { + event.preventDefault(); + scrollPrevious(); + } else if (event.key === "ArrowRight") { + event.preventDefault(); + scrollNext(); + } + }, + [scrollPrevious, scrollNext], + ); + + useEffect(() => { + if (!api || !setApi) { + return; + } + + setApi(api); + }, [api, setApi]); + + useEffect(() => { + if (!api) { + return; + } + + onSelect(api); + api.on("reInit", onSelect); + api.on("select", onSelect); + + return () => { + api && api.off("select", onSelect); + }; + }, [api, onSelect]); + + return ( + // @ts-expect-error TODO: fix + <CarouselContext + value={{ + api: api, + canScrollNext, + canScrollPrev: canScrollPrevious, + carouselRef: carouselRef, + opts, + orientation: + orientation || + (opts && opts.axis === "y" ? "vertical" : "horizontal"), + scrollNext, + scrollPrev: scrollPrevious, + }} + > + <div + aria-roledescription="carousel" + className={cn("relative", className)} + onKeyDownCapture={handleKeyDown} + ref={ref} + role="region" + {...props} + > + {children} + </div> + </CarouselContext> + ); + }, +); + +Carousel.displayName = "Carousel"; + +const CarouselContent = forwardRef< + HTMLDivElement, + HTMLAttributes<HTMLDivElement> +>(({ className, ...props }, ref) => { + const { carouselRef, orientation } = useCarousel(); + + return ( + <div className="overflow-hidden" ref={carouselRef}> + <div + className={cn( + "flex", + orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col", + className, + )} + ref={ref} + {...props} + /> + </div> + ); +}); + +CarouselContent.displayName = "CarouselContent"; + +const CarouselItem = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>( + ({ className, ...props }, ref) => { + const { orientation } = useCarousel(); + + return ( + <div + aria-roledescription="slide" + className={cn( + "min-w-0 shrink-0 grow-0 basis-full", + orientation === "horizontal" ? "pl-4" : "pt-4", + className, + )} + ref={ref} + role="group" + {...props} + /> + ); + }, +); + +CarouselItem.displayName = "CarouselItem"; + +const CarouselPrevious = forwardRef< + HTMLButtonElement, + ComponentProps<typeof Button> +>(({ className, size = "icon", variant = "outline", ...props }, ref) => { + const { canScrollPrev, orientation, scrollPrev } = useCarousel(); + + return ( + <Button + className={cn( + "absolute size-8 rounded-full", + orientation === "horizontal" + ? "-left-12 top-1/2 -translate-y-1/2" + : "-top-12 left-1/2 -translate-x-1/2 rotate-90", + className, + )} + disabled={!canScrollPrev} + onClick={scrollPrev} + ref={ref} + size={size} + variant={variant} + {...props} + > + <ArrowLeftIcon className="size-4" /> + <span className="sr-only">Previous slide</span> + </Button> + ); +}); + +CarouselPrevious.displayName = "CarouselPrevious"; + +const CarouselNext = forwardRef< + HTMLButtonElement, + ComponentProps<typeof Button> +>(({ className, size = "icon", variant = "outline", ...props }, ref) => { + const { canScrollNext, orientation, scrollNext } = useCarousel(); + + return ( + <Button + className={cn( + "absolute size-8 rounded-full", + orientation === "horizontal" + ? "-right-12 top-1/2 -translate-y-1/2" + : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90", + className, + )} + disabled={!canScrollNext} + onClick={scrollNext} + ref={ref} + size={size} + variant={variant} + {...props} + > + <ArrowRightIcon className="size-4" /> + <span className="sr-only">Next slide</span> + </Button> + ); +}); + +CarouselNext.displayName = "CarouselNext"; + +export { + Carousel, + type CarouselApi, + CarouselContent, + CarouselItem, + CarouselNext, + CarouselPrevious, +}; diff --git a/addons/browser/reliverse/ui/Chart.tsx b/addons/browser/reliverse/ui/Chart.tsx new file mode 100644 index 00000000..67d09efb --- /dev/null +++ b/addons/browser/reliverse/ui/Chart.tsx @@ -0,0 +1,413 @@ +"use client"; + +import * as React from "react"; + +import { cn } from "@/browser/shared/utils"; +import * as RechartsPrimitive from "recharts"; + +// Format: { THEME_NAME: CSS_SELECTOR } +const THEMES = { dark: ".dark", light: "" } as const; + +export type ChartConfig = { + [k in string]: { + icon?: React.ComponentType; + label?: React.ReactNode; + } & ( + | { color?: never; theme: Record<keyof typeof THEMES, string> } + | { color?: string; theme?: never } + ); +}; + +type ChartContextProps = { + config: ChartConfig; +}; + +const ChartContext = React.createContext<ChartContextProps | null>(null); + +function useChart() { + const context = React.useContext(ChartContext); + + if (!context) { + throw new Error("useChart must be used within a <ChartContainer />"); + } + + return context; +} + +const ChartContainer = React.forwardRef< + HTMLDivElement, + { + children: React.ComponentProps< + typeof RechartsPrimitive.ResponsiveContainer + >["children"]; + config: ChartConfig; + } & React.ComponentProps<"div"> +>(({ id, children, className, config, ...props }, ref) => { + const uniqueId = React.useId(); + const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`; + + return ( + <ChartContext.Provider value={{ config }}> + <div + className={cn( + ` + flex aspect-video justify-center text-xs + + [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground + + [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 + + [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border + + [&_.recharts-dot[stroke='#fff']]:stroke-transparent + + [&_.recharts-layer]:outline-none + + [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border + + [&_.recharts-radial-bar-background-sector]:fill-muted + + [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted + + [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border + + [&_.recharts-sector[stroke='#fff']]:stroke-transparent + + [&_.recharts-sector]:outline-none + + [&_.recharts-surface]:outline-none + `, + className, + )} + data-chart={chartId} + ref={ref} + {...props} + > + <ChartStyle config={config} id={chartId} /> + <RechartsPrimitive.ResponsiveContainer> + {children} + </RechartsPrimitive.ResponsiveContainer> + </div> + </ChartContext.Provider> + ); +}); + +ChartContainer.displayName = "Chart"; + +const ChartStyle = ({ id, config }: { config: ChartConfig; id: string }) => { + const colorConfig = Object.entries(config).filter( + ([_, config]) => config.theme || config.color, + ); + + if (colorConfig.length === 0) { + return null; + } + + return ( + <style + dangerouslySetInnerHTML={{ + __html: Object.entries(THEMES) + .map( + ([theme, prefix]) => ` +${prefix} [data-chart=${id}] { +${colorConfig + .map(([key, itemConfig]) => { + const color = + itemConfig.theme?.[theme as keyof typeof itemConfig.theme] || + itemConfig.color; + + return color ? ` --color-${key}: ${color};` : null; + }) + .join("\n")} +} +`, + ) + .join("\n"), + }} + /> + ); +}; + +const ChartTooltip = RechartsPrimitive.Tooltip; + +const ChartTooltipContent = React.forwardRef< + HTMLDivElement, + { + hideIndicator?: boolean; + hideLabel?: boolean; + indicator?: "dashed" | "dot" | "line"; + labelKey?: string; + nameKey?: string; + } & React.ComponentProps<"div"> & + React.ComponentProps<typeof RechartsPrimitive.Tooltip> +>( + ( + { + active, + className, + color, + formatter, + hideIndicator = false, + hideLabel = false, + indicator = "dot", + label, + labelClassName, + labelFormatter, + labelKey, + nameKey, + payload, + }, + ref, + ) => { + const { config } = useChart(); + + const tooltipLabel = React.useMemo(() => { + if (hideLabel || !payload?.length) { + return null; + } + + const [item] = payload; + const key = `${labelKey || (item && (item.dataKey || item.name)) || "value"}`; + const itemConfig = getPayloadConfigFromPayload(config, item, key); + const value = + !labelKey && typeof label === "string" + ? config[label as keyof typeof config]?.label || label + : itemConfig?.label; + + if (labelFormatter) { + return ( + <div className={cn("font-medium", labelClassName)}> + {labelFormatter(value, payload)} + </div> + ); + } + + if (!value) { + return null; + } + + return <div className={cn("font-medium", labelClassName)}>{value}</div>; + }, [ + label, + labelFormatter, + payload, + hideLabel, + labelClassName, + config, + labelKey, + ]); + + if (!active || !payload?.length) { + return null; + } + + const nestLabel = payload.length === 1 && indicator !== "dot"; + + return ( + <div + className={cn( + ` + grid min-w-32 items-start gap-1.5 rounded-lg border border-border/50 + bg-background px-2.5 py-1.5 text-xs shadow-xl + `, + className, + )} + ref={ref} + > + {!nestLabel ? tooltipLabel : null} + <div className="grid gap-1.5"> + {payload.map((item, index) => { + const key = `${nameKey || item.name || item.dataKey || "value"}`; + const itemConfig = getPayloadConfigFromPayload(config, item, key); + const indicatorColor = color || item.payload.fill || item.color; + + return ( + <div + className={cn( + ` + flex w-full flex-wrap items-stretch gap-2 + + [&>svg]:size-2.5 [&>svg]:text-muted-foreground + `, + indicator === "dot" && "items-center", + )} + key={item.dataKey} + > + {formatter && item?.value !== undefined && item.name ? ( + formatter(item.value, item.name, item, index, item.payload) + ) : ( + <> + {itemConfig?.icon ? ( + <itemConfig.icon /> + ) : ( + !hideIndicator && ( + <div + className={cn( + ` + shrink-0 rounded-[2px] border-[--color-border] + bg-[--color-bg] + `, + { + "h-2.5 w-2.5": indicator === "dot", + "my-0.5": nestLabel && indicator === "dashed", + "w-0 border-[1.5px] border-dashed bg-transparent": + indicator === "dashed", + "w-1": indicator === "line", + }, + )} + style={ + { + "--color-bg": indicatorColor, + "--color-border": indicatorColor, + } as React.CSSProperties + } + /> + ) + )} + <div + className={cn( + "flex flex-1 justify-between leading-none", + nestLabel ? "items-end" : "items-center", + )} + > + <div className="grid gap-1.5"> + {nestLabel ? tooltipLabel : null} + <span className="text-muted-foreground"> + {itemConfig?.label || item.name} + </span> + </div> + {item.value && ( + <span + className={` + font-mono font-medium tabular-nums text-foreground + `} + > + {item.value.toLocaleString()} + </span> + )} + </div> + </> + )} + </div> + ); + })} + </div> + </div> + ); + }, +); + +ChartTooltipContent.displayName = "ChartTooltip"; + +const ChartLegend = RechartsPrimitive.Legend; + +const ChartLegendContent = React.forwardRef< + HTMLDivElement, + { + hideIcon?: boolean; + nameKey?: string; + } & Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & + React.ComponentProps<"div"> +>( + ( + { className, hideIcon = false, nameKey, payload, verticalAlign = "bottom" }, + ref, + ) => { + const { config } = useChart(); + + if (!payload?.length) { + return null; + } + + return ( + <div + className={cn( + "flex items-center justify-center gap-4", + verticalAlign === "top" ? "pb-3" : "pt-3", + className, + )} + ref={ref} + > + {payload.map((item) => { + const key = `${nameKey || item.dataKey || "value"}`; + const itemConfig = getPayloadConfigFromPayload(config, item, key); + + return ( + <div + className={cn( + ` + flex items-center gap-1.5 + + [&>svg]:size-3 [&>svg]:text-muted-foreground + `, + )} + key={item.value} + > + {itemConfig?.icon && !hideIcon ? ( + <itemConfig.icon /> + ) : ( + <div + className="size-2 shrink-0 rounded-[2px]" + style={{ + backgroundColor: item.color, + }} + /> + )} + {itemConfig?.label} + </div> + ); + })} + </div> + ); + }, +); + +ChartLegendContent.displayName = "ChartLegend"; + +// Helper to extract item config from a payload. +function getPayloadConfigFromPayload( + config: ChartConfig, + payload: unknown, + key: string, +) { + if (typeof payload !== "object" || payload === null) { + return; + } + + const payloadPayload = + "payload" in payload && + typeof payload.payload === "object" && + payload.payload !== null + ? payload.payload + : undefined; + + let configLabelKey: string = key; + + if ( + key in payload && + typeof payload[key as keyof typeof payload] === "string" + ) { + configLabelKey = payload[key as keyof typeof payload] as string; + } else if ( + payloadPayload && + key in payloadPayload && + typeof payloadPayload[key as keyof typeof payloadPayload] === "string" + ) { + configLabelKey = payloadPayload[ + key as keyof typeof payloadPayload + ] as string; + } + + return configLabelKey in config + ? config[configLabelKey] + : config[key as keyof typeof config]; +} + +export { + ChartContainer, + ChartLegend, + ChartLegendContent, + ChartStyle, + ChartTooltip, + ChartTooltipContent, +}; diff --git a/addons/browser/reliverse/ui/Checkbox.tsx b/addons/browser/reliverse/ui/Checkbox.tsx new file mode 100644 index 00000000..e9bf79e1 --- /dev/null +++ b/addons/browser/reliverse/ui/Checkbox.tsx @@ -0,0 +1,42 @@ +"use client"; + +import type { ComponentPropsWithoutRef, ComponentRef } from "react"; +import { forwardRef } from "react"; + +import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; +import { CheckIcon } from "@radix-ui/react-icons"; + +import { cn } from "~/utils"; + +const Checkbox = forwardRef< + ComponentRef<typeof CheckboxPrimitive.Root>, + ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> +>(({ className, ...props }, ref) => ( + <CheckboxPrimitive.Root + className={cn( + ` + peer size-4 shrink-0 rounded-sm border border-primary shadow + + data-[state=checked]:bg-primary + data-[state=checked]:text-primary-foreground + + disabled:cursor-not-allowed disabled:opacity-50 + + focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring + `, + className, + )} + ref={ref} + {...props} + > + <CheckboxPrimitive.Indicator + className={cn("flex items-center justify-center text-current")} + > + <CheckIcon className="size-4" /> + </CheckboxPrimitive.Indicator> + </CheckboxPrimitive.Root> +)); + +Checkbox.displayName = CheckboxPrimitive.Root.displayName; + +export { Checkbox }; diff --git a/addons/browser/reliverse/ui/Command.tsx b/addons/browser/reliverse/ui/Command.tsx new file mode 100644 index 00000000..ebdbbf0f --- /dev/null +++ b/addons/browser/reliverse/ui/Command.tsx @@ -0,0 +1,218 @@ +"use client"; + +import type { + ComponentPropsWithoutRef, + ComponentRef, + HTMLAttributes, +} from "react"; +import { forwardRef } from "react"; + +import type { DialogPosition } from "@/browser/reliverse/ui/Dialog"; +import type { DialogProps } from "@radix-ui/react-dialog"; + +import { + Dialog, + DialogContent, + DialogTitle, +} from "@/browser/reliverse/ui/Dialog"; +import { Separator } from "@/browser/reliverse/ui/Separator"; +import { MagnifyingGlassIcon } from "@radix-ui/react-icons"; +import { Command as CommandPrimitive } from "cmdk"; + +import { cn } from "~/utils"; + +const Command = forwardRef< + ComponentRef<typeof CommandPrimitive>, + ComponentPropsWithoutRef<typeof CommandPrimitive> +>(({ className, ...props }, ref) => ( + <CommandPrimitive + className={cn( + ` + flex size-full flex-col overflow-hidden rounded-lg bg-popover + text-popover-foreground + `, + className, + )} + ref={ref} + {...props} + /> +)); + +Command.displayName = CommandPrimitive.displayName; + +type CommandDialogProps = DialogPosition & DialogProps; + +const CommandDialog = ({ + children, + position = "default", + ...props +}: CommandDialogProps) => ( + <Dialog {...props}> + <DialogContent className="overflow-hidden p-0" position={position}> + <DialogTitle + className={` + pl-3 pt-3 text-base font-medium text-muted-foreground + `} + > + ↓↑ Command Menu + </DialogTitle> + <Separator /> + <Command + className={` + [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium + [&_[cmdk-group-heading]]:text-muted-foreground + + [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 + + [&_[cmdk-group]]:px-2 + + [&_[cmdk-input-wrapper]_svg]:size-5 + + [&_[cmdk-input]]:h-12 + + [&_[cmdk-item]_svg]:size-5 + + [&_[cmdk-item]]:p-2 + `} + > + {children} + </Command> + </DialogContent> + </Dialog> +); + +const CommandInput = forwardRef< + ComponentRef<typeof CommandPrimitive.Input>, + ComponentPropsWithoutRef<typeof CommandPrimitive.Input> +>(({ className, ...props }, ref) => ( + <div className="flex items-center border-b px-3 pb-3" cmdk-input-wrapper=""> + <MagnifyingGlassIcon className="mr-2 size-4 shrink-0 opacity-50" /> + <CommandPrimitive.Input + className={cn( + ` + flex h-10 w-full rounded-lg bg-transparent py-3 text-sm outline-none + + disabled:cursor-not-allowed disabled:opacity-50 + + placeholder:text-muted-foreground + `, + className, + )} + ref={ref} + {...props} + /> + </div> +)); + +CommandInput.displayName = CommandPrimitive.Input.displayName; + +const CommandList = forwardRef< + ComponentRef<typeof CommandPrimitive.List>, + ComponentPropsWithoutRef<typeof CommandPrimitive.List> +>(({ className, ...props }, ref) => ( + <CommandPrimitive.List + className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)} + ref={ref} + {...props} + /> +)); + +CommandList.displayName = CommandPrimitive.List.displayName; + +const CommandEmpty = forwardRef< + ComponentRef<typeof CommandPrimitive.Empty>, + ComponentPropsWithoutRef<typeof CommandPrimitive.Empty> +>((props, ref) => ( + <CommandPrimitive.Empty + className="py-6 text-center text-sm" + ref={ref} + {...props} + /> +)); + +CommandEmpty.displayName = CommandPrimitive.Empty.displayName; + +const CommandGroup = forwardRef< + ComponentRef<typeof CommandPrimitive.Group>, + ComponentPropsWithoutRef<typeof CommandPrimitive.Group> +>(({ className, ...props }, ref) => ( + <CommandPrimitive.Group + className={cn( + ` + overflow-hidden p-1 text-foreground + + [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 + [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium + [&_[cmdk-group-heading]]:text-muted-foreground + `, + className, + )} + ref={ref} + {...props} + /> +)); + +CommandGroup.displayName = CommandPrimitive.Group.displayName; + +const CommandSeparator = forwardRef< + ComponentRef<typeof CommandPrimitive.Separator>, + ComponentPropsWithoutRef<typeof CommandPrimitive.Separator> +>(({ className, ...props }, ref) => ( + <CommandPrimitive.Separator + className={cn("-mx-1 h-px bg-border", className)} + ref={ref} + {...props} + /> +)); + +CommandSeparator.displayName = CommandPrimitive.Separator.displayName; + +const CommandItem = forwardRef< + ComponentRef<typeof CommandPrimitive.Item>, + ComponentPropsWithoutRef<typeof CommandPrimitive.Item> +>(({ className, ...props }, ref) => ( + <CommandPrimitive.Item + className={cn( + ` + relative flex cursor-default select-none items-center rounded-sm px-2 + py-1.5 text-sm outline-none + + aria-selected:bg-accent aria-selected:text-accent-foreground + + data-[disabled]:pointer-events-none data-[disabled]:opacity-50 + `, + className, + )} + ref={ref} + {...props} + /> +)); + +CommandItem.displayName = CommandPrimitive.Item.displayName; + +const CommandShortcut = ({ + className, + ...props +}: HTMLAttributes<HTMLSpanElement>) => ( + <span + className={cn( + "ml-auto text-xs tracking-widest text-muted-foreground", + className, + )} + {...props} + /> +); + +CommandShortcut.displayName = "CommandShortcut"; + +export { + Command, + CommandDialog, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, + CommandShortcut, +}; diff --git a/addons/browser/reliverse/ui/Dialog.tsx b/addons/browser/reliverse/ui/Dialog.tsx new file mode 100644 index 00000000..1170e6cb --- /dev/null +++ b/addons/browser/reliverse/ui/Dialog.tsx @@ -0,0 +1,181 @@ +"use client"; + +import type { + ComponentPropsWithoutRef, + ComponentRef, + HTMLAttributes, +} from "react"; +import { forwardRef } from "react"; + +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { Cross2Icon } from "@radix-ui/react-icons"; + +import { cn } from "~/utils"; + +export type DialogPosition = { + position?: "default" | "top"; +}; + +const Dialog = DialogPrimitive.Root; + +const DialogTrigger = DialogPrimitive.Trigger; + +const DialogPortal = ({ ...props }: DialogPrimitive.DialogPortalProps) => ( + <DialogPrimitive.Portal {...props} /> +); + +DialogPortal.displayName = DialogPrimitive.Portal.displayName; + +const DialogOverlay = forwardRef< + ComponentRef<typeof DialogPrimitive.Overlay>, + ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> +>(({ className, ...props }, ref) => ( + <DialogPrimitive.Overlay + className={cn( + ` + fixed inset-0 z-50 bg-background/80 backdrop-blur-sm + + data-[state=closed]:animate-out data-[state=closed]:fade-out-0 + + data-[state=open]:animate-in data-[state=open]:fade-in-0 + `, + className, + )} + ref={ref} + {...props} + /> +)); + +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = forwardRef< + ComponentRef<typeof DialogPrimitive.Content>, + ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & DialogPosition +>(({ children, className, position, ...props }, ref) => ( + <DialogPortal> + <DialogOverlay /> + <DialogPrimitive.Content + className={cn( + ` + fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 + -translate-y-2/4 gap-4 border bg-background p-6 shadow-lg duration-200 + + data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 + data-[state=closed]:slide-out-to-left-1/2 + data-[state=closed]:slide-out-to-top-[48%] + + data-[state=open]:animate-in data-[state=open]:fade-in-0 + data-[state=open]:zoom-in-95 data-[state=open]:slide-in-from-left-1/2 + data-[state=open]:slide-in-from-top-[48%] + + md:w-full + + sm:rounded-lg + `, + position === "default" && "data-[state=closed]:animate-out", + position === "top" && "top-44 translate-y-0", + className, + )} + ref={ref} + {...props} + > + {children} + <DialogPrimitive.Close + className={` + absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background + transition-opacity + + data-[state=open]:bg-accent data-[state=open]:text-muted-foreground + + disabled:pointer-events-none + + focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 + + hover:opacity-100 + `} + > + <Cross2Icon className="size-4" /> + <span className="sr-only">Close</span> + </DialogPrimitive.Close> + </DialogPrimitive.Content> + </DialogPortal> +)); + +DialogContent.displayName = DialogPrimitive.Content.displayName; + +const DialogHeader = ({ + className, + ...props +}: HTMLAttributes<HTMLDivElement>) => ( + <div + className={cn( + ` + flex flex-col space-y-1.5 text-center + + sm:text-left + `, + className, + )} + {...props} + /> +); + +DialogHeader.displayName = "DialogHeader"; + +const DialogFooter = ({ + className, + ...props +}: HTMLAttributes<HTMLDivElement>) => ( + <div + className={cn( + ` + flex flex-col-reverse + + sm:flex-row sm:justify-end sm:space-x-2 + `, + className, + )} + {...props} + /> +); + +DialogFooter.displayName = "DialogFooter"; + +const DialogTitle = forwardRef< + ComponentRef<typeof DialogPrimitive.Title>, + ComponentPropsWithoutRef<typeof DialogPrimitive.Title> +>(({ className, ...props }, ref) => ( + <DialogPrimitive.Title + className={cn( + "text-lg font-semibold leading-none tracking-tight", + className, + )} + ref={ref} + {...props} + /> +)); + +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = forwardRef< + ComponentRef<typeof DialogPrimitive.Description>, + ComponentPropsWithoutRef<typeof DialogPrimitive.Description> +>(({ className, ...props }, ref) => ( + <DialogPrimitive.Description + className={cn("text-sm text-muted-foreground", className)} + ref={ref} + {...props} + /> +)); + +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +}; diff --git a/addons/browser/reliverse/ui/Drawer.tsx b/addons/browser/reliverse/ui/Drawer.tsx new file mode 100644 index 00000000..6641d075 --- /dev/null +++ b/addons/browser/reliverse/ui/Drawer.tsx @@ -0,0 +1,123 @@ +"use client"; + +import type { + ComponentPropsWithoutRef, + ComponentRef, + HTMLAttributes, +} from "react"; +import { forwardRef } from "react"; + +import { Drawer as DrawerPrimitive } from "vaul"; + +import { cn } from "~/utils"; + +const Drawer = DrawerPrimitive.Root; + +const DrawerTrigger = DrawerPrimitive.Trigger; + +const DrawerClose = DrawerPrimitive.Close; + +const DrawerContent = forwardRef< + ComponentRef<typeof DrawerPrimitive.Content>, + ComponentPropsWithoutRef<typeof DrawerPrimitive.Content> +>(({ children, className, ...props }, ref) => ( + <DrawerPrimitive.Portal> + <DrawerPrimitive.Overlay className="fixed inset-0 z-50 bg-zinc-950/60" /> + <DrawerPrimitive.Content + className={cn( + ` + fixed inset-x-0 bottom-0 z-50 mt-24 h-[96%] rounded-t-[10px] + bg-background + `, + className, + )} + ref={ref} + {...props} + > + <div + className={` + absolute left-1/2 top-3 h-2 w-[100px] -translate-x-1/2 rounded-full + bg-muted + `} + /> + {children} + </DrawerPrimitive.Content> + </DrawerPrimitive.Portal> +)); + +DrawerContent.displayName = "DrawerContent"; + +const DrawerHeader = ({ + className, + ...props +}: HTMLAttributes<HTMLDivElement>) => ( + <div + className={cn( + ` + flex flex-col space-y-2 text-center + + sm:text-left + `, + className, + )} + {...props} + /> +); + +DrawerHeader.displayName = "DrawerHeader"; + +const DrawerFooter = ({ + className, + ...props +}: HTMLAttributes<HTMLDivElement>) => ( + <div + className={cn( + ` + flex flex-col-reverse + + sm:flex-row sm:justify-end sm:space-x-2 + `, + className, + )} + {...props} + /> +); + +DrawerFooter.displayName = "DrawerFooter"; + +const DrawerTitle = forwardRef< + ComponentRef<typeof DrawerPrimitive.Title>, + ComponentPropsWithoutRef<typeof DrawerPrimitive.Title> +>(({ className, ...props }, ref) => ( + <DrawerPrimitive.Title + className={cn("text-lg font-semibold text-foreground", className)} + ref={ref} + {...props} + /> +)); + +DrawerTitle.displayName = DrawerPrimitive.Title.displayName; + +const DrawerDescription = forwardRef< + ComponentRef<typeof DrawerPrimitive.Description>, + ComponentPropsWithoutRef<typeof DrawerPrimitive.Description> +>(({ className, ...props }, ref) => ( + <DrawerPrimitive.Description + className={cn("text-sm text-muted-foreground", className)} + ref={ref} + {...props} + /> +)); + +DrawerDescription.displayName = DrawerPrimitive.Description.displayName; + +export { + Drawer, + DrawerClose, + DrawerContent, + DrawerDescription, + DrawerFooter, + DrawerHeader, + DrawerTitle, + DrawerTrigger, +}; /// / @see https://github.com/shadcn-ui/ui/blob/main/apps/www/components/drawer.tsx// diff --git a/addons/browser/reliverse/ui/Dropdown.tsx b/addons/browser/reliverse/ui/Dropdown.tsx new file mode 100644 index 00000000..ef432e93 --- /dev/null +++ b/addons/browser/reliverse/ui/Dropdown.tsx @@ -0,0 +1,290 @@ +"use client"; + +import type { + ComponentPropsWithoutRef, + ComponentRef, + HTMLAttributes, +} from "react"; +import { forwardRef } from "react"; + +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; +import { + CheckIcon, + ChevronRightIcon, + DotFilledIcon, +} from "@radix-ui/react-icons"; + +import { cn } from "~/utils"; + +const DropdownMenu = DropdownMenuPrimitive.Root; + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; + +const DropdownMenuGroup = DropdownMenuPrimitive.Group; + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal; + +const DropdownMenuSub = DropdownMenuPrimitive.Sub; + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; + +const DropdownMenuSubTrigger = forwardRef< + ComponentRef<typeof DropdownMenuPrimitive.SubTrigger>, + { + inset?: boolean; + } & ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> +>(({ children, className, inset, ...props }, ref) => ( + <DropdownMenuPrimitive.SubTrigger + className={cn( + ` + flex cursor-default select-none items-center rounded-sm px-2 py-1.5 + text-sm outline-none + + data-[state=open]:bg-accent + + focus:bg-accent + `, + inset && "pl-8", + className, + )} + ref={ref} + {...props} + > + {children} + <ChevronRightIcon className="ml-auto size-4" /> + </DropdownMenuPrimitive.SubTrigger> +)); + +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName; + +const DropdownMenuSubContent = forwardRef< + ComponentRef<typeof DropdownMenuPrimitive.SubContent>, + ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent> +>(({ className, ...props }, ref) => ( + <DropdownMenuPrimitive.SubContent + className={cn( + ` + z-50 min-w-32 overflow-hidden rounded-lg border bg-popover p-1 + text-popover-foreground shadow-lg + + data-[side=bottom]:slide-in-from-top-2 + + data-[side=left]:slide-in-from-right-2 + + data-[side=right]:slide-in-from-left-2 + + data-[side=top]:slide-in-from-bottom-2 + + data-[state=closed]:animate-out data-[state=closed]:fade-out-0 + data-[state=closed]:zoom-out-95 + + data-[state=open]:animate-in data-[state=open]:fade-in-0 + data-[state=open]:zoom-in-95 + `, + className, + )} + ref={ref} + {...props} + /> +)); + +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName; + +const DropdownMenuContent = forwardRef< + ComponentRef<typeof DropdownMenuPrimitive.Content>, + ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> +>(({ className, sideOffset = 4, ...props }, ref) => ( + <DropdownMenuPrimitive.Portal> + <DropdownMenuPrimitive.Content + className={cn( + ` + z-50 min-w-32 overflow-hidden rounded-lg border bg-popover p-1 + text-popover-foreground shadow-md + `, + ` + data-[side=bottom]:slide-in-from-top-2 + + data-[side=left]:slide-in-from-right-2 + + data-[side=right]:slide-in-from-left-2 + + data-[side=top]:slide-in-from-bottom-2 + + data-[state=closed]:animate-out data-[state=closed]:fade-out-0 + data-[state=closed]:zoom-out-95 + + data-[state=open]:animate-in data-[state=open]:fade-in-0 + data-[state=open]:zoom-in-95 + `, + className, + )} + ref={ref} + sideOffset={sideOffset} + {...props} + /> + </DropdownMenuPrimitive.Portal> +)); + +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; + +const DropdownMenuItem = forwardRef< + ComponentRef<typeof DropdownMenuPrimitive.Item>, + { + inset?: boolean; + } & ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> +>(({ className, inset, ...props }, ref) => ( + <DropdownMenuPrimitive.Item + className={cn( + ` + relative flex cursor-default select-none items-center rounded-sm px-2 + py-1.5 text-sm outline-none transition-colors + + data-[disabled]:pointer-events-none data-[disabled]:opacity-50 + + focus:bg-accent focus:text-accent-foreground + `, + inset && "pl-8", + className, + )} + ref={ref} + {...props} + /> +)); + +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; + +const DropdownMenuCheckboxItem = forwardRef< + ComponentRef<typeof DropdownMenuPrimitive.CheckboxItem>, + ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem> +>(({ checked, children, className, ...props }, ref) => ( + <DropdownMenuPrimitive.CheckboxItem + checked={checked} + className={cn( + ` + relative flex cursor-default select-none items-center rounded-sm py-1.5 + pl-8 pr-2 text-sm outline-none transition-colors + + data-[disabled]:pointer-events-none data-[disabled]:opacity-50 + + focus:bg-accent focus:text-accent-foreground + `, + className, + )} + ref={ref} + {...props} + > + <span + className={` + absolute left-2 flex size-3.5 items-center + justify-center + `} + > + <DropdownMenuPrimitive.ItemIndicator> + <CheckIcon className="size-4" /> + </DropdownMenuPrimitive.ItemIndicator> + </span> + {children} + </DropdownMenuPrimitive.CheckboxItem> +)); + +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName; + +const DropdownMenuRadioItem = forwardRef< + ComponentRef<typeof DropdownMenuPrimitive.RadioItem>, + ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem> +>(({ children, className, ...props }, ref) => ( + <DropdownMenuPrimitive.RadioItem + className={cn( + ` + relative flex cursor-default select-none items-center rounded-sm py-1.5 + pl-8 pr-2 text-sm outline-none transition-colors + + data-[disabled]:pointer-events-none data-[disabled]:opacity-50 + + focus:bg-accent focus:text-accent-foreground + `, + className, + )} + ref={ref} + {...props} + > + <span + className={` + absolute left-2 flex size-3.5 items-center + justify-center + `} + > + <DropdownMenuPrimitive.ItemIndicator> + <DotFilledIcon className="size-4 fill-current" /> + </DropdownMenuPrimitive.ItemIndicator> + </span> + {children} + </DropdownMenuPrimitive.RadioItem> +)); + +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; + +const DropdownMenuLabel = forwardRef< + ComponentRef<typeof DropdownMenuPrimitive.Label>, + { + inset?: boolean; + } & ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> +>(({ className, inset, ...props }, ref) => ( + <DropdownMenuPrimitive.Label + className={cn( + "px-2 py-1.5 text-sm font-semibold", + inset && "pl-8", + className, + )} + ref={ref} + {...props} + /> +)); + +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; + +const DropdownMenuSeparator = forwardRef< + ComponentRef<typeof DropdownMenuPrimitive.Separator>, + ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator> +>(({ className, ...props }, ref) => ( + <DropdownMenuPrimitive.Separator + className={cn("-mx-1 my-1 h-px bg-muted", className)} + ref={ref} + {...props} + /> +)); + +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; + +const DropdownMenuShortcut = ({ + className, + ...props +}: HTMLAttributes<HTMLSpanElement>) => ( + <span + className={cn("ml-auto text-xs tracking-widest opacity-60", className)} + {...props} + /> +); + +DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; + +export { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuPortal, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuTrigger, +}; diff --git a/addons/browser/reliverse/ui/Form.tsx b/addons/browser/reliverse/ui/Form.tsx new file mode 100644 index 00000000..2ab15c1a --- /dev/null +++ b/addons/browser/reliverse/ui/Form.tsx @@ -0,0 +1,220 @@ +"use client"; + +import type { + ComponentPropsWithoutRef, + ComponentRef, + HTMLAttributes, +} from "react"; +import { createContext, forwardRef, use, useId } from "react"; +import type { ControllerProps, FieldPath, FieldValues } from "react-hook-form"; +import { Controller, FormProvider, useFormContext } from "react-hook-form"; + +import type * as LabelPrimitive from "@radix-ui/react-label"; + +import { Label } from "@/browser/reliverse/ui/Label"; +import { Slot } from "@radix-ui/react-slot"; + +import { cn } from "~/utils"; + +const Form = FormProvider; + +type FormFieldContextValue< + TFieldValues extends FieldValues, + TName extends FieldPath<TFieldValues>, +> = { + name: TName; +}; + +const FormFieldContext = createContext< + FormFieldContextValue<FieldValues, string> +>({} as FormFieldContextValue<FieldValues, string>); + +const FormField = < + TFieldValues extends FieldValues, + TName extends FieldPath<TFieldValues>, +>({ + ...props +}: ControllerProps<TFieldValues, TName>) => ( + // @ts-expect-error TODO: fix + <FormFieldContext + value={{ + name: props.name, + }} + > + <Controller {...props} /> + </FormFieldContext> +); + +const useFormField = () => { + const fieldContext = use(FormFieldContext); + + const { formState, getFieldState } = useFormContext(); + + const fieldState = getFieldState(fieldContext.name, formState); + + if (!fieldContext) { + throw new Error("useFormField should be used within <FormField>"); + } + + const { id } = use(FormItemContext); + + return { + id, + name: fieldContext.name, + formDescriptionId: `${id}-form-item-description`, + formItemId: `${id}-form-item`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + }; +}; + +type FormItemContextValue = { + id: string; +}; + +const FormItemContext = createContext<FormItemContextValue>({ + // +} as FormItemContextValue); + +const FormItem = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>( + ({ className, ...props }, ref) => { + const id = useId(); + + return ( + // @ts-expect-error TODO: fix + <FormItemContext + value={{ + id, + }} + > + <div className={cn("space-y-2", className)} ref={ref} {...props} /> + </FormItemContext> + ); + }, +); + +FormItem.displayName = "FormItem"; + +const FormLabel = forwardRef< + ComponentRef<typeof LabelPrimitive.Root>, + ComponentPropsWithoutRef<typeof LabelPrimitive.Root> +>(({ className, ...props }, ref) => { + const { error, formItemId } = useFormField(); + + return ( + <Label + className={cn(error && "text-destructive", className)} + htmlFor={formItemId} + ref={ref} + {...props} + /> + ); +}); + +FormLabel.displayName = "FormLabel"; + +const FormControl = forwardRef< + ComponentRef<typeof Slot>, + ComponentPropsWithoutRef<typeof Slot> +>(({ ...props }, ref) => { + const { error, formDescriptionId, formItemId, formMessageId } = + useFormField(); + + return ( + <Slot + aria-describedby={ + !error + ? String(formDescriptionId) + : `${formDescriptionId} ${formMessageId}` + } + aria-invalid={!!error} + id={formItemId} + ref={ref} + {...props} + /> + ); +}); + +FormControl.displayName = "FormControl"; + +const FormDescription = forwardRef< + HTMLParagraphElement, + HTMLAttributes<HTMLParagraphElement> +>(({ className, ...props }, ref) => { + const { formDescriptionId } = useFormField(); + + return ( + <p + className={cn("text-[0.8rem] text-muted-foreground", className)} + id={formDescriptionId} + ref={ref} + {...props} + /> + ); +}); + +FormDescription.displayName = "FormDescription"; + +const FormMessage = forwardRef< + HTMLParagraphElement, + HTMLAttributes<HTMLParagraphElement> +>(({ children, className, ...props }, ref) => { + const { error, formMessageId } = useFormField(); + const body = error ? String(error?.message) : children; + + if (!body) { + return null; + } + + return ( + <p + className={cn("text-[0.8rem] font-medium text-destructive", className)} + id={formMessageId} + ref={ref} + {...props} + > + {body} + </p> + ); +}); + +FormMessage.displayName = "FormMessage"; + +const UncontrolledFormMessage = forwardRef< + HTMLParagraphElement, + { + message?: string; + } & HTMLAttributes<HTMLParagraphElement> +>(({ children, className, message, ...props }, ref) => { + const { formMessageId } = useFormField(); + const body = message ? String(message) : children; + + if (!body) { + return null; + } + + return ( + <p + className={cn("text-sm font-medium text-destructive", className)} + id={formMessageId} + ref={ref} + {...props} + > + {body} + </p> + ); +}); + +UncontrolledFormMessage.displayName = "UncontrolledFormMessage"; + +export { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, + UncontrolledFormMessage, + useFormField, +}; diff --git a/addons/browser/reliverse/ui/Icon.tsx b/addons/browser/reliverse/ui/Icon.tsx new file mode 100644 index 00000000..168ca96c --- /dev/null +++ b/addons/browser/reliverse/ui/Icon.tsx @@ -0,0 +1,35 @@ +import type { ReactElement } from "react"; + +// Image metadata +export const size = { + height: 32, + width: 32, +}; + +export const contentType = "image/png"; + +type ImageOptions = { + height: number; + width: number; +}; + +export declare function ImageResponse( + element: ReactElement, + options: ImageOptions, +): ReactElement; + +// Image generation +export default function Icon() { + return ImageResponse( + <div + className={` + flex size-full items-center justify-center bg-black text-[24px] + leading-8 text-white + `} + > + S + </div>, // ^ ImageResponse JSX element + // ImageResponse options: + size, + ); +} diff --git a/addons/browser/reliverse/ui/Input.tsx b/addons/browser/reliverse/ui/Input.tsx new file mode 100644 index 00000000..e243c160 --- /dev/null +++ b/addons/browser/reliverse/ui/Input.tsx @@ -0,0 +1,38 @@ +"use client"; + +import type { InputHTMLAttributes } from "react"; +import { forwardRef } from "react"; + +import { cn } from "~/utils"; + +type InputProps = InputHTMLAttributes<HTMLInputElement>; + +const Input = forwardRef<HTMLInputElement, InputProps>( + ({ className, type, ...props }, ref) => ( + <input + className={cn( + ` + flex h-10 w-full rounded-lg border border-input bg-background px-3 + py-2 text-sm ring-offset-background + + disabled:cursor-not-allowed disabled:opacity-50 + + file:border-0 file:bg-transparent file:text-sm file:font-medium + + focus-visible:outline-none focus-visible:ring-2 + focus-visible:ring-ring focus-visible:ring-offset-2 + + placeholder:text-muted-foreground + `, + className, + )} + ref={ref} + type={type} + {...props} + /> + ), +); + +Input.displayName = "Input"; + +export { Input }; diff --git a/addons/browser/reliverse/ui/Label.tsx b/addons/browser/reliverse/ui/Label.tsx new file mode 100644 index 00000000..fd5aa8ee --- /dev/null +++ b/addons/browser/reliverse/ui/Label.tsx @@ -0,0 +1,33 @@ +"use client"; + +import type { ComponentPropsWithoutRef, ComponentRef } from "react"; +import { forwardRef } from "react"; + +import type { VariantProps } from "class-variance-authority"; + +import * as LabelPrimitive from "@radix-ui/react-label"; +import { cva } from "class-variance-authority"; + +import { cn } from "~/utils"; + +const labelVariants = cva(` + text-sm font-medium leading-none + + peer-disabled:cursor-not-allowed peer-disabled:opacity-70 +`); + +const Label = forwardRef< + ComponentRef<typeof LabelPrimitive.Root>, + ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & + VariantProps<typeof labelVariants> +>(({ className, ...props }, ref) => ( + <LabelPrimitive.Root + className={cn(labelVariants(), className)} + ref={ref} + {...props} + /> +)); + +Label.displayName = LabelPrimitive.Root.displayName; + +export { Label }; diff --git a/addons/browser/reliverse/ui/Password.tsx b/addons/browser/reliverse/ui/Password.tsx new file mode 100644 index 00000000..a902d4c0 --- /dev/null +++ b/addons/browser/reliverse/ui/Password.tsx @@ -0,0 +1,58 @@ +"use client"; + +import type { InputHTMLAttributes } from "react"; +import { forwardRef, useState } from "react"; + +import { Button } from "@/browser/reliverse/ui/Button"; +import { Input } from "@/browser/reliverse/ui/Input"; + +import { Icons } from "~/components/Common/Icons"; +import { cn } from "~/utils"; + +type PasswordInputProps = { + className?: string; +} & InputHTMLAttributes<HTMLInputElement>; + +const PasswordInput = forwardRef<HTMLInputElement, PasswordInputProps>( + ({ className, ...props }, ref) => { + const [showPassword, setShowPassword] = useState(false); + + return ( + <div className="relative"> + <Input + className={cn("pr-10", className)} + ref={ref} + type={showPassword ? "text" : "password"} + {...props} + /> + <Button + className={` + absolute right-0 top-0 h-full px-3 py-2 + + hover:bg-transparent + `} + disabled={props.value === "" || props.disabled} + onClick={() => { + setShowPassword((previous) => !previous); + }} + size="sm" + type="button" + variant="ghost" + > + {showPassword ? ( + <Icons.hide aria-hidden="true" className="size-4" /> + ) : ( + <Icons.view aria-hidden="true" className="size-4" /> + )} + <span className="sr-only"> + {showPassword ? "Hide password" : "Show password"} + </span> + </Button> + </div> + ); + }, +); + +PasswordInput.displayName = "PasswordInput"; + +export { PasswordInput }; diff --git a/addons/browser/reliverse/ui/Popover.tsx b/addons/browser/reliverse/ui/Popover.tsx new file mode 100644 index 00000000..327fe73c --- /dev/null +++ b/addons/browser/reliverse/ui/Popover.tsx @@ -0,0 +1,51 @@ +"use client"; + +import type { ComponentPropsWithoutRef, ComponentRef } from "react"; +import { forwardRef } from "react"; + +import * as PopoverPrimitive from "@radix-ui/react-popover"; + +import { cn } from "~/utils"; + +const Popover = PopoverPrimitive.Root; + +const PopoverTrigger = PopoverPrimitive.Trigger; + +const PopoverContent = forwardRef< + ComponentRef<typeof PopoverPrimitive.Content>, + ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> +>(({ align = "center", className, sideOffset = 4, ...props }, ref) => ( + <PopoverPrimitive.Portal> + <PopoverPrimitive.Content + align={align} + className={cn( + ` + z-50 w-72 rounded-lg border bg-popover p-4 text-popover-foreground + shadow-md outline-none + + data-[side=bottom]:slide-in-from-top-2 + + data-[side=left]:slide-in-from-right-2 + + data-[side=right]:slide-in-from-left-2 + + data-[side=top]:slide-in-from-bottom-2 + + data-[state=closed]:animate-out data-[state=closed]:fade-out-0 + data-[state=closed]:zoom-out-95 + + data-[state=open]:animate-in data-[state=open]:fade-in-0 + data-[state=open]:zoom-in-95 + `, + className, + )} + ref={ref} + sideOffset={sideOffset} + {...props} + /> + </PopoverPrimitive.Portal> +)); + +PopoverContent.displayName = PopoverPrimitive.Content.displayName; + +export { Popover, PopoverContent, PopoverTrigger }; diff --git a/addons/browser/reliverse/ui/Popup.tsx b/addons/browser/reliverse/ui/Popup.tsx new file mode 100644 index 00000000..7168a278 --- /dev/null +++ b/addons/browser/reliverse/ui/Popup.tsx @@ -0,0 +1,38 @@ +"use client"; + +import { useState } from "react"; + +import { + Sheet, + SheetClose, + SheetContent, + SheetHeader, + SheetTitle, + SheetTrigger, +} from "@/browser/reliverse/ui/Sheet"; + +export const Popup = () => { + const [isOpen, setIsOpen] = useState(false); + + return ( + <Sheet open={isOpen}> + <SheetTrigger> + <button + onClick={() => { + setIsOpen(true); + }} + type="button" + > + Open Popup + </button> + </SheetTrigger> + <SheetContent> + <SheetHeader> + <SheetTitle>Popup Title</SheetTitle> + <SheetClose /> + </SheetHeader> + <p>Popup content goes here.</p> + </SheetContent> + </Sheet> + ); +}; diff --git a/addons/browser/reliverse/ui/Scroll-Area.tsx b/addons/browser/reliverse/ui/Scroll-Area.tsx new file mode 100644 index 00000000..d02e6d1f --- /dev/null +++ b/addons/browser/reliverse/ui/Scroll-Area.tsx @@ -0,0 +1,55 @@ +"use client"; + +import type { ComponentPropsWithoutRef, ComponentRef } from "react"; +import { forwardRef } from "react"; + +import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"; + +import { cn } from "~/utils"; + +const ScrollArea = forwardRef< + ComponentRef<typeof ScrollAreaPrimitive.Root>, + { + orientation?: "horizontal" | "vertical"; + scrollBarClassName?: string; + } & ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> +>(({ children, className, orientation, scrollBarClassName, ...props }, ref) => ( + <ScrollAreaPrimitive.Root + className={cn("relative overflow-hidden", className)} + ref={ref} + {...props} + > + <ScrollAreaPrimitive.Viewport className="size-full rounded-[inherit]"> + {children} + </ScrollAreaPrimitive.Viewport> + <ScrollBar className={cn(scrollBarClassName)} orientation={orientation} /> + <ScrollAreaPrimitive.Corner /> + </ScrollAreaPrimitive.Root> +)); + +ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName; + +const ScrollBar = forwardRef< + ComponentRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>, + ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar> +>(({ className, orientation = "vertical", ...props }, ref) => ( + <ScrollAreaPrimitive.ScrollAreaScrollbar + className={cn( + "flex touch-none select-none transition-colors", + orientation === "vertical" && + "h-full w-2.5 border-l border-l-transparent p-px", + orientation === "horizontal" && + "h-2.5 border-t border-t-transparent p-px", + className, + )} + orientation={orientation} + ref={ref} + {...props} + > + <ScrollAreaPrimitive.ScrollAreaThumb /> + </ScrollAreaPrimitive.ScrollAreaScrollbar> +)); + +ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName; + +export { ScrollArea, ScrollBar }; diff --git a/addons/browser/reliverse/ui/Select.tsx b/addons/browser/reliverse/ui/Select.tsx new file mode 100644 index 00000000..e674536f --- /dev/null +++ b/addons/browser/reliverse/ui/Select.tsx @@ -0,0 +1,177 @@ +"use client"; + +import type { ComponentPropsWithoutRef, ComponentRef } from "react"; +import { forwardRef } from "react"; + +import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons"; +import * as SelectPrimitive from "@radix-ui/react-select"; + +import { cn } from "~/utils"; + +const Select = SelectPrimitive.Root; + +const SelectGroup = SelectPrimitive.Group; + +const SelectValue = SelectPrimitive.Value; + +const SelectTrigger = forwardRef< + ComponentRef<typeof SelectPrimitive.Trigger>, + ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> +>(({ children, className, ...props }, ref) => ( + <SelectPrimitive.Trigger + className={cn( + ` + flex h-9 w-full items-center justify-between rounded-lg border + border-input bg-transparent px-3 py-2 text-sm shadow-sm + ring-offset-background + + disabled:cursor-not-allowed disabled:opacity-50 + + focus:outline-none focus:ring-1 focus:ring-ring + + placeholder:text-muted-foreground + `, + className, + )} + ref={ref} + {...props} + > + {children} + <SelectPrimitive.Icon asChild> + <CaretSortIcon className="size-4 opacity-50" /> + </SelectPrimitive.Icon> + </SelectPrimitive.Trigger> +)); + +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; + +const SelectContent = forwardRef< + ComponentRef<typeof SelectPrimitive.Content>, + ComponentPropsWithoutRef<typeof SelectPrimitive.Content> +>(({ children, className, position = "popper", ...props }, ref) => ( + <SelectPrimitive.Portal> + <SelectPrimitive.Content + className={cn( + ` + relative z-50 min-w-32 overflow-hidden rounded-lg border bg-popover + text-popover-foreground shadow-md + + data-[side=bottom]:slide-in-from-top-2 + + data-[side=left]:slide-in-from-right-2 + + data-[side=right]:slide-in-from-left-2 + + data-[side=top]:slide-in-from-bottom-2 + + data-[state=closed]:animate-out data-[state=closed]:fade-out-0 + data-[state=closed]:zoom-out-95 + + data-[state=open]:animate-in data-[state=open]:fade-in-0 + data-[state=open]:zoom-in-95 + `, + position === "popper" && + ` + data-[side=bottom]:translate-y-1 + + data-[side=left]:-translate-x-1 + + data-[side=right]:translate-x-1 + + data-[side=top]:-translate-y-1 + `, + className, + )} + position={position} + ref={ref} + {...props} + > + <SelectPrimitive.Viewport + className={cn( + "p-1", + position === "popper" && + ` + h-[var(--radix-select-trigger-height)] w-full + min-w-[var(--radix-select-trigger-width)] + `, + )} + > + {children} + </SelectPrimitive.Viewport> + </SelectPrimitive.Content> + </SelectPrimitive.Portal> +)); + +SelectContent.displayName = SelectPrimitive.Content.displayName; + +const SelectLabel = forwardRef< + ComponentRef<typeof SelectPrimitive.Label>, + ComponentPropsWithoutRef<typeof SelectPrimitive.Label> +>(({ className, ...props }, ref) => ( + <SelectPrimitive.Label + className={cn("px-2 py-1.5 text-sm font-semibold", className)} + ref={ref} + {...props} + /> +)); + +SelectLabel.displayName = SelectPrimitive.Label.displayName; + +const SelectItem = forwardRef< + ComponentRef<typeof SelectPrimitive.Item>, + ComponentPropsWithoutRef<typeof SelectPrimitive.Item> +>(({ children, className, ...props }, ref) => ( + <SelectPrimitive.Item + className={cn( + ` + relative flex w-full cursor-default select-none items-center rounded-sm + py-1.5 pl-2 pr-8 text-sm outline-none + + data-[disabled]:pointer-events-none data-[disabled]:opacity-50 + + focus:bg-accent focus:text-accent-foreground + `, + className, + )} + ref={ref} + {...props} + > + <span + className={` + absolute right-2 flex size-3.5 items-center + justify-center + `} + > + <SelectPrimitive.ItemIndicator> + <CheckIcon className="size-4" /> + </SelectPrimitive.ItemIndicator> + </span> + <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText> + </SelectPrimitive.Item> +)); + +SelectItem.displayName = SelectPrimitive.Item.displayName; + +const SelectSeparator = forwardRef< + ComponentRef<typeof SelectPrimitive.Separator>, + ComponentPropsWithoutRef<typeof SelectPrimitive.Separator> +>(({ className, ...props }, ref) => ( + <SelectPrimitive.Separator + className={cn("-mx-1 my-1 h-px bg-muted", className)} + ref={ref} + {...props} + /> +)); + +SelectSeparator.displayName = SelectPrimitive.Separator.displayName; + +export { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectSeparator, + SelectTrigger, + SelectValue, +}; diff --git a/addons/browser/reliverse/ui/Separator.tsx b/addons/browser/reliverse/ui/Separator.tsx new file mode 100644 index 00000000..8078ec01 --- /dev/null +++ b/addons/browser/reliverse/ui/Separator.tsx @@ -0,0 +1,34 @@ +"use client"; + +import type { ComponentPropsWithoutRef, ComponentRef } from "react"; +import { forwardRef } from "react"; + +import * as SeparatorPrimitive from "@radix-ui/react-separator"; + +import { cn } from "~/utils"; + +const Separator = forwardRef< + ComponentRef<typeof SeparatorPrimitive.Root>, + ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root> +>( + ( + { className, decorative = true, orientation = "horizontal", ...props }, + ref, + ) => ( + <SeparatorPrimitive.Root + className={cn( + "shrink-0 bg-border", + orientation === "horizontal" ? "h-px w-full" : "h-full w-px", + className, + )} + decorative={decorative} + orientation={orientation} + ref={ref} + {...props} + /> + ), +); + +Separator.displayName = SeparatorPrimitive.Root.displayName; + +export { Separator }; diff --git a/addons/browser/reliverse/ui/Sheet.tsx b/addons/browser/reliverse/ui/Sheet.tsx new file mode 100644 index 00000000..796f990b --- /dev/null +++ b/addons/browser/reliverse/ui/Sheet.tsx @@ -0,0 +1,221 @@ +"use client"; + +import type { + ComponentPropsWithoutRef, + ComponentRef, + HTMLAttributes, +} from "react"; +import { forwardRef } from "react"; + +import type { VariantProps } from "class-variance-authority"; + +import * as SheetPrimitive from "@radix-ui/react-dialog"; +import { Cross2Icon } from "@radix-ui/react-icons"; +import { cva } from "class-variance-authority"; + +import { cn } from "~/utils"; + +const Sheet = SheetPrimitive.Root; + +const SheetTrigger = SheetPrimitive.Trigger; + +const SheetClose = SheetPrimitive.Close; + +const SheetPortal = ({ ...props }: SheetPrimitive.DialogPortalProps) => ( + <SheetPrimitive.Portal {...props} /> +); + +SheetPortal.displayName = SheetPrimitive.Portal.displayName; + +const SheetOverlay = forwardRef< + ComponentRef<typeof SheetPrimitive.Overlay>, + ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay> +>(({ className, ...props }, ref) => ( + <SheetPrimitive.Overlay + className={cn( + ` + fixed inset-0 z-50 bg-background/80 backdrop-blur-sm + + data-[state=closed]:animate-out data-[state=closed]:fade-out-0 + + data-[state=open]:animate-in data-[state=open]:fade-in-0 + `, + className, + )} + {...props} + ref={ref} + /> +)); + +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; + +const sheetVariants = cva( + ` + fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out + + data-[state=closed]:duration-300 data-[state=closed]:animate-out + + data-[state=open]:duration-500 data-[state=open]:animate-in + `, + { + defaultVariants: { + side: "right", + }, + variants: { + side: { + bottom: ` + inset-x-0 bottom-0 border-t + + data-[state=closed]:slide-out-to-bottom + + data-[state=open]:slide-in-from-bottom + `, + left: ` + inset-y-0 left-0 h-full w-3/4 border-r + + data-[state=closed]:slide-out-to-left + + data-[state=open]:slide-in-from-left + + sm:max-w-sm + `, + right: ` + inset-y-0 right-0 h-full w-3/4 border-l + + data-[state=closed]:slide-out-to-right + + data-[state=open]:slide-in-from-right + + sm:max-w-sm + `, + top: ` + inset-x-0 top-0 border-b + + data-[state=closed]:slide-out-to-top + + data-[state=open]:slide-in-from-top + `, + }, + }, + }, +); + +type SheetContentProps = {} & ComponentPropsWithoutRef< + typeof SheetPrimitive.Content +> & + VariantProps<typeof sheetVariants>; + +const SheetContent = forwardRef< + ComponentRef<typeof SheetPrimitive.Content>, + SheetContentProps +>(({ children, className, side = "right", ...props }, ref) => ( + <SheetPortal> + <SheetOverlay /> + <SheetPrimitive.Content + className={cn( + sheetVariants({ + side, + }), + className, + )} + ref={ref} + {...props} + > + {children} + <SheetPrimitive.Close + className={` + absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background + transition-opacity + + data-[state=open]:bg-secondary + + disabled:pointer-events-none + + focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 + + hover:opacity-100 + `} + > + <Cross2Icon className="size-4" /> + <span className="sr-only">Close</span> + </SheetPrimitive.Close> + </SheetPrimitive.Content> + </SheetPortal> +)); + +SheetContent.displayName = SheetPrimitive.Content.displayName; + +const SheetHeader = ({ + className, + ...props +}: HTMLAttributes<HTMLDivElement>) => ( + <div + className={cn( + ` + flex flex-col space-y-2 text-center + + sm:text-left + `, + className, + )} + {...props} + /> +); + +SheetHeader.displayName = "SheetHeader"; + +const SheetFooter = ({ + className, + ...props +}: HTMLAttributes<HTMLDivElement>) => ( + <div + className={cn( + ` + flex flex-col-reverse + + sm:flex-row sm:justify-end sm:space-x-2 + `, + className, + )} + {...props} + /> +); + +SheetFooter.displayName = "SheetFooter"; + +const SheetTitle = forwardRef< + ComponentRef<typeof SheetPrimitive.Title>, + ComponentPropsWithoutRef<typeof SheetPrimitive.Title> +>(({ className, ...props }, ref) => ( + <SheetPrimitive.Title + className={cn("text-lg font-semibold text-foreground", className)} + ref={ref} + {...props} + /> +)); + +SheetTitle.displayName = SheetPrimitive.Title.displayName; + +const SheetDescription = forwardRef< + ComponentRef<typeof SheetPrimitive.Description>, + ComponentPropsWithoutRef<typeof SheetPrimitive.Description> +>(({ className, ...props }, ref) => ( + <SheetPrimitive.Description + className={cn("text-sm text-muted-foreground", className)} + ref={ref} + {...props} + /> +)); + +SheetDescription.displayName = SheetPrimitive.Description.displayName; + +export { + Sheet, + SheetClose, + SheetContent, + SheetDescription, + SheetFooter, + SheetHeader, + SheetTitle, + SheetTrigger, +}; diff --git a/addons/browser/reliverse/ui/Skeleton.tsx b/addons/browser/reliverse/ui/Skeleton.tsx new file mode 100644 index 00000000..88bbd75c --- /dev/null +++ b/addons/browser/reliverse/ui/Skeleton.tsx @@ -0,0 +1,14 @@ +import type { HTMLAttributes } from "react"; + +import { cn } from "~/utils"; + +function Skeleton({ className, ...props }: HTMLAttributes<HTMLDivElement>) { + return ( + <div + className={cn("animate-pulse rounded-lg bg-primary/10", className)} + {...props} + /> + ); +} + +export { Skeleton }; diff --git a/addons/browser/reliverse/ui/Slider.tsx b/addons/browser/reliverse/ui/Slider.tsx new file mode 100644 index 00000000..238b98b5 --- /dev/null +++ b/addons/browser/reliverse/ui/Slider.tsx @@ -0,0 +1,76 @@ +"use client"; + +import type { ComponentPropsWithoutRef, ComponentRef } from "react"; +import { forwardRef } from "react"; + +import * as SliderPrimitive from "@radix-ui/react-slider"; + +import { cn } from "~/utils"; + +const Slider = forwardRef< + ComponentRef<typeof SliderPrimitive.Root>, + { + thickness?: "default" | "thin"; + variant?: "default" | "range"; + } & ComponentPropsWithoutRef<typeof SliderPrimitive.Root> +>( + ( + { className, thickness = "default", variant = "default", ...props }, + ref, + ) => ( + <SliderPrimitive.Root + className={cn( + "relative flex w-full touch-none select-none items-center", + className, + )} + ref={ref} + {...props} + > + <SliderPrimitive.Track + className={cn( + ` + relative h-1.5 w-full grow overflow-hidden rounded-full + bg-primary/20 + `, + thickness === "thin" && "h-0.5", + )} + > + <SliderPrimitive.Range className="absolute h-full bg-primary" /> + </SliderPrimitive.Track> + <SliderPrimitive.Thumb + className={cn( + ` + block size-4 rounded-full border border-primary/50 bg-background + shadow transition-colors + + disabled:pointer-events-none disabled:opacity-50 + + focus-visible:outline-none focus-visible:ring-1 + focus-visible:ring-ring + `, + thickness === "thin" && "size-3.5", + )} + /> + {variant === "range" && ( + <SliderPrimitive.Thumb + className={cn( + ` + block size-5 rounded-full border-2 border-primary bg-background + ring-offset-background transition-colors + + disabled:pointer-events-none disabled:opacity-50 + + focus-visible:outline-none focus-visible:ring-2 + focus-visible:ring-ring focus-visible:ring-offset-2 + `, + thickness === "thin" && "size-3.5", + )} + /> + )} + </SliderPrimitive.Root> + ), +); + +Slider.displayName = SliderPrimitive.Root.displayName; + +export { Slider }; diff --git a/addons/browser/reliverse/ui/Switch.tsx b/addons/browser/reliverse/ui/Switch.tsx new file mode 100644 index 00000000..57d6f3e6 --- /dev/null +++ b/addons/browser/reliverse/ui/Switch.tsx @@ -0,0 +1,49 @@ +"use client"; + +import type { ComponentPropsWithoutRef, ComponentRef } from "react"; +import { forwardRef } from "react"; + +import * as SwitchPrimitives from "@radix-ui/react-switch"; + +import { cn } from "~/utils"; + +const Switch = forwardRef< + ComponentRef<typeof SwitchPrimitives.Root>, + ComponentPropsWithoutRef<typeof SwitchPrimitives.Root> +>(({ className, ...props }, ref) => ( + <SwitchPrimitives.Root + className={cn( + ` + peer inline-flex h-[20px] w-[36px] shrink-0 cursor-pointer items-center + rounded-full border-2 border-transparent shadow-sm transition-colors + + data-[state=checked]:bg-primary + + data-[state=unchecked]:bg-input + + disabled:cursor-not-allowed disabled:opacity-50 + + focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring + focus-visible:ring-offset-2 focus-visible:ring-offset-background + `, + className, + )} + {...props} + ref={ref} + > + <SwitchPrimitives.Thumb + className={cn(` + pointer-events-none block size-4 rounded-full bg-background shadow-lg + ring-0 transition-transform + + data-[state=checked]:translate-x-4 + + data-[state=unchecked]:translate-x-0 + `)} + /> + </SwitchPrimitives.Root> +)); + +Switch.displayName = SwitchPrimitives.Root.displayName; + +export { Switch }; diff --git a/addons/browser/reliverse/ui/Table.tsx b/addons/browser/reliverse/ui/Table.tsx new file mode 100644 index 00000000..62b0261c --- /dev/null +++ b/addons/browser/reliverse/ui/Table.tsx @@ -0,0 +1,139 @@ +import type { HTMLAttributes, TdHTMLAttributes, ThHTMLAttributes } from "react"; +import { forwardRef } from "react"; + +import { cn } from "~/utils"; + +const Table = forwardRef<HTMLTableElement, HTMLAttributes<HTMLTableElement>>( + ({ className, ...props }, ref) => ( + <div className="w-full overflow-auto"> + <table + className={cn("w-full caption-bottom text-sm", className)} + ref={ref} + {...props} + /> + </div> + ), +); + +Table.displayName = "Table"; + +const TableHeader = forwardRef< + HTMLTableSectionElement, + HTMLAttributes<HTMLTableSectionElement> +>(({ className, ...props }, ref) => ( + <thead className={cn("[&_tr]:border-b", className)} ref={ref} {...props} /> +)); + +TableHeader.displayName = "TableHeader"; + +const TableBody = forwardRef< + HTMLTableSectionElement, + HTMLAttributes<HTMLTableSectionElement> +>(({ className, ...props }, ref) => ( + <tbody + className={cn("[&_tr:last-child]:border-0", className)} + ref={ref} + {...props} + /> +)); + +TableBody.displayName = "TableBody"; + +const TableFooter = forwardRef< + HTMLTableSectionElement, + HTMLAttributes<HTMLTableSectionElement> +>(({ className, ...props }, ref) => ( + <tfoot + className={cn("bg-primary font-medium text-primary-foreground", className)} + ref={ref} + {...props} + /> +)); + +TableFooter.displayName = "TableFooter"; + +const TableRow = forwardRef< + HTMLTableRowElement, + HTMLAttributes<HTMLTableRowElement> +>(({ className, ...props }, ref) => ( + <tr + className={cn( + ` + border-b transition-colors + + data-[state=selected]:bg-muted + + hover:bg-muted/50 + `, + className, + )} + ref={ref} + {...props} + /> +)); + +TableRow.displayName = "TableRow"; + +const TableHead = forwardRef< + HTMLTableCellElement, + ThHTMLAttributes<HTMLTableCellElement> +>(({ className, ...props }, ref) => ( + <th + className={cn( + ` + h-12 px-4 text-left align-middle font-medium text-muted-foreground + + [&:has([role=checkbox])]:pr-0 + `, + className, + )} + ref={ref} + {...props} + /> +)); + +TableHead.displayName = "TableHead"; + +const TableCell = forwardRef< + HTMLTableCellElement, + TdHTMLAttributes<HTMLTableCellElement> +>(({ className, ...props }, ref) => ( + <td + className={cn( + ` + p-4 align-middle + + [&:has([role=checkbox])]:pr-0 + `, + className, + )} + ref={ref} + {...props} + /> +)); + +TableCell.displayName = "TableCell"; + +const TableCaption = forwardRef< + HTMLTableCaptionElement, + HTMLAttributes<HTMLTableCaptionElement> +>(({ className, ...props }, ref) => ( + <caption + className={cn("mt-4 text-sm text-muted-foreground", className)} + ref={ref} + {...props} + /> +)); + +TableCaption.displayName = "TableCaption"; + +export { + Table, + TableBody, + TableCaption, + TableCell, + TableFooter, + TableHead, + TableHeader, + TableRow, +}; diff --git a/addons/browser/reliverse/ui/Tabs.tsx b/addons/browser/reliverse/ui/Tabs.tsx new file mode 100644 index 00000000..732ec3e3 --- /dev/null +++ b/addons/browser/reliverse/ui/Tabs.tsx @@ -0,0 +1,79 @@ +"use client"; + +import type { ComponentPropsWithoutRef, ComponentRef } from "react"; +import { forwardRef } from "react"; + +import * as TabsPrimitive from "@radix-ui/react-tabs"; + +import { cn } from "~/utils"; + +const Tabs = TabsPrimitive.Root; + +const TabsList = forwardRef< + ComponentRef<typeof TabsPrimitive.List>, + ComponentPropsWithoutRef<typeof TabsPrimitive.List> +>(({ className, ...props }, ref) => ( + <TabsPrimitive.List + className={cn( + ` + inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 + text-muted-foreground + `, + className, + )} + ref={ref} + {...props} + /> +)); + +TabsList.displayName = TabsPrimitive.List.displayName; + +const TabsTrigger = forwardRef< + ComponentRef<typeof TabsPrimitive.Trigger>, + ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger> +>(({ className, ...props }, ref) => ( + <TabsPrimitive.Trigger + className={cn( + ` + inline-flex items-center justify-center whitespace-nowrap rounded-lg + px-3 py-1 text-sm font-medium ring-offset-background transition-all + + data-[state=active]:bg-background data-[state=active]:text-foreground + data-[state=active]:shadow + + disabled:pointer-events-none disabled:opacity-50 + + focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring + focus-visible:ring-offset-2 + `, + className, + )} + ref={ref} + {...props} + /> +)); + +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName; + +const TabsContent = forwardRef< + ComponentRef<typeof TabsPrimitive.Content>, + ComponentPropsWithoutRef<typeof TabsPrimitive.Content> +>(({ className, ...props }, ref) => ( + <TabsPrimitive.Content + className={cn( + ` + mt-2 ring-offset-background + + focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring + focus-visible:ring-offset-2 + `, + className, + )} + ref={ref} + {...props} + /> +)); + +TabsContent.displayName = TabsPrimitive.Content.displayName; + +export { Tabs, TabsContent, TabsList, TabsTrigger }; diff --git a/addons/browser/reliverse/ui/Text-Area.tsx b/addons/browser/reliverse/ui/Text-Area.tsx new file mode 100644 index 00000000..0524802f --- /dev/null +++ b/addons/browser/reliverse/ui/Text-Area.tsx @@ -0,0 +1,33 @@ +import type { TextareaHTMLAttributes } from "react"; +import { forwardRef } from "react"; + +import { cn } from "~/utils"; + +type TextareaProps = TextareaHTMLAttributes<HTMLTextAreaElement>; + +const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>( + ({ className, ...props }, ref) => ( + <textarea + className={cn( + ` + flex min-h-[60px] w-full rounded-lg border border-input bg-transparent + px-3 py-2 text-sm shadow-sm + + disabled:cursor-not-allowed disabled:opacity-50 + + focus-visible:outline-none focus-visible:ring-1 + focus-visible:ring-ring + + placeholder:text-muted-foreground + `, + className, + )} + ref={ref} + {...props} + /> + ), +); + +Textarea.displayName = "Textarea"; + +export { Textarea }; diff --git a/addons/browser/reliverse/ui/Toast.tsx b/addons/browser/reliverse/ui/Toast.tsx new file mode 100644 index 00000000..a0c0276d --- /dev/null +++ b/addons/browser/reliverse/ui/Toast.tsx @@ -0,0 +1,192 @@ +"use client"; + +import * as React from "react"; + +import type { VariantProps } from "class-variance-authority"; + +import { cn } from "@/browser/shared/utils"; +import * as ToastPrimitives from "@radix-ui/react-toast"; +import { cva } from "class-variance-authority"; +import { X } from "lucide-react"; + +const ToastProvider = ToastPrimitives.Provider; + +const ToastViewport = React.forwardRef< + React.ElementRef<typeof ToastPrimitives.Viewport>, + React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport> +>(({ className, ...props }, ref) => ( + <ToastPrimitives.Viewport + ref={ref} + className={cn( + ` + fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 + + md:max-w-[420px] + + sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col + `, + className, + )} + {...props} + /> +)); + +ToastViewport.displayName = ToastPrimitives.Viewport.displayName; + +const toastVariants = cva( + ` + group pointer-events-auto relative flex w-full items-center justify-between + space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg + transition-all + + data-[state=closed]:animate-out data-[state=closed]:fade-out-80 + data-[state=closed]:slide-out-to-right-full + + data-[state=open]:animate-in data-[state=open]:slide-in-from-top-full + data-[state=open]:sm:slide-in-from-bottom-full + + data-[swipe=cancel]:translate-x-0 + + data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] + data-[swipe=end]:animate-out + + data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] + data-[swipe=move]:transition-none + `, + { + variants: { + variant: { + default: "border bg-background text-foreground", + destructive: ` + destructive group border-destructive bg-destructive + text-destructive-foreground + `, + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +const Toast = React.forwardRef< + React.ElementRef<typeof ToastPrimitives.Root>, + React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & + VariantProps<typeof toastVariants> +>(({ className, variant, ...props }, ref) => { + return ( + <ToastPrimitives.Root + ref={ref} + className={cn(toastVariants({ variant }), className)} + {...props} + /> + ); +}); + +Toast.displayName = ToastPrimitives.Root.displayName; + +const ToastAction = React.forwardRef< + React.ElementRef<typeof ToastPrimitives.Action>, + React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action> +>(({ className, ...props }, ref) => ( + <ToastPrimitives.Action + ref={ref} + className={cn( + ` + inline-flex h-8 shrink-0 items-center justify-center rounded-md border + bg-transparent px-3 text-sm font-medium ring-offset-background + transition-colors + + disabled:pointer-events-none disabled:opacity-50 + + focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 + + group-[.destructive]:border-muted/40 + group-[.destructive]:hover:border-destructive/30 + group-[.destructive]:hover:bg-destructive + group-[.destructive]:hover:text-destructive-foreground + group-[.destructive]:focus:ring-destructive + + hover:bg-secondary + `, + className, + )} + {...props} + /> +)); + +ToastAction.displayName = ToastPrimitives.Action.displayName; + +const ToastClose = React.forwardRef< + React.ElementRef<typeof ToastPrimitives.Close>, + React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close> +>(({ className, ...props }, ref) => ( + <ToastPrimitives.Close + ref={ref} + className={cn( + ` + absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 + transition-opacity + + focus:opacity-100 focus:outline-none focus:ring-2 + + group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 + group-[.destructive]:focus:ring-red-400 + group-[.destructive]:focus:ring-offset-red-600 + + group-hover:opacity-100 + + hover:text-foreground + `, + className, + )} + toast-close="" + {...props} + > + <X className="size-4" /> + </ToastPrimitives.Close> +)); + +ToastClose.displayName = ToastPrimitives.Close.displayName; + +const ToastTitle = React.forwardRef< + React.ElementRef<typeof ToastPrimitives.Title>, + React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title> +>(({ className, ...props }, ref) => ( + <ToastPrimitives.Title + ref={ref} + className={cn("text-sm font-semibold", className)} + {...props} + /> +)); + +ToastTitle.displayName = ToastPrimitives.Title.displayName; + +const ToastDescription = React.forwardRef< + React.ElementRef<typeof ToastPrimitives.Description>, + React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description> +>(({ className, ...props }, ref) => ( + <ToastPrimitives.Description + ref={ref} + className={cn("text-sm opacity-90", className)} + {...props} + /> +)); + +ToastDescription.displayName = ToastPrimitives.Description.displayName; + +type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>; + +type ToastActionElement = React.ReactElement<typeof ToastAction>; + +export { + Toast, + ToastAction, + type ToastActionElement, + ToastClose, + ToastDescription, + type ToastProps, + ToastProvider, + ToastTitle, + ToastViewport, +}; diff --git a/addons/browser/reliverse/ui/Toaster.tsx b/addons/browser/reliverse/ui/Toaster.tsx new file mode 100644 index 00000000..4cc4fec3 --- /dev/null +++ b/addons/browser/reliverse/ui/Toaster.tsx @@ -0,0 +1,36 @@ +"use client"; + +import { useToast } from "@/browser/reliverse/ui/use-toast"; + +import { + Toast, + ToastClose, + ToastDescription, + ToastProvider, + ToastTitle, + ToastViewport, +} from "./Toast"; + +export function Toaster() { + const { toasts } = useToast(); + + return ( + <ToastProvider> + {toasts.map(function ({ id, title, description, action, ...props }) { + return ( + <Toast key={id} {...props}> + <div className="grid gap-1"> + {title && <ToastTitle>{title}</ToastTitle>} + {description && ( + <ToastDescription>{description}</ToastDescription> + )} + </div> + {action} + <ToastClose /> + </Toast> + ); + })} + <ToastViewport /> + </ToastProvider> + ); +} diff --git a/addons/browser/reliverse/ui/Tooltip.tsx b/addons/browser/reliverse/ui/Tooltip.tsx new file mode 100644 index 00000000..5005a9ab --- /dev/null +++ b/addons/browser/reliverse/ui/Tooltip.tsx @@ -0,0 +1,46 @@ +"use client"; + +import type { ComponentPropsWithoutRef, ElementRef } from "react"; +import { forwardRef } from "react"; + +import { cn } from "@/browser/shared/utils"; +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; + +const TooltipProvider = TooltipPrimitive.Provider; + +const Tooltip = TooltipPrimitive.Root; + +const TooltipTrigger = TooltipPrimitive.Trigger; + +const TooltipContent = forwardRef< + ElementRef<typeof TooltipPrimitive.Content>, + ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> +>(({ className, sideOffset = 4, ...props }, ref) => ( + <TooltipPrimitive.Content + className={cn( + ` + z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm + text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 + + data-[side=bottom]:slide-in-from-top-2 + + data-[side=left]:slide-in-from-right-2 + + data-[side=right]:slide-in-from-left-2 + + data-[side=top]:slide-in-from-bottom-2 + + data-[state=closed]:animate-out data-[state=closed]:fade-out-0 + data-[state=closed]:zoom-out-95 + `, + className, + )} + ref={ref} + sideOffset={sideOffset} + {...props} + /> +)); + +TooltipContent.displayName = TooltipPrimitive.Content.displayName; + +export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }; diff --git a/addons/browser/reliverse/ui/use-toast.ts b/addons/browser/reliverse/ui/use-toast.ts new file mode 100644 index 00000000..db1c3213 --- /dev/null +++ b/addons/browser/reliverse/ui/use-toast.ts @@ -0,0 +1,208 @@ +"use client"; + +// Inspired by react-hot-toast library +import * as React from "react"; + +import type { + ToastActionElement, + ToastProps, +} from "@/browser/reliverse/ui/Toast"; + +const TOAST_LIMIT = 1; +const TOAST_REMOVE_DELAY = 1000000; + +type ToasterToast = { + action?: ToastActionElement; + description?: React.ReactNode; + id: string; + title?: React.ReactNode; +} & ToastProps; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const actionTypes = { + ADD_TOAST: "ADD_TOAST", + UPDATE_TOAST: "UPDATE_TOAST", + DISMISS_TOAST: "DISMISS_TOAST", + REMOVE_TOAST: "REMOVE_TOAST", +} as const; + +let count = 0; + +function toastGenId() { + count = (count + 1) % Number.MAX_SAFE_INTEGER; + + return count.toString(); +} + +type ActionType = typeof actionTypes; + +type Action = + | { + toast: Partial<ToasterToast>; + type: ActionType["UPDATE_TOAST"]; + } + | { + toast: ToasterToast; + type: ActionType["ADD_TOAST"]; + } + | { + toastId?: ToasterToast["id"]; + type: ActionType["DISMISS_TOAST"]; + } + | { + toastId?: ToasterToast["id"]; + type: ActionType["REMOVE_TOAST"]; + }; + +type State = { + toasts: ToasterToast[]; +}; + +const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>(); + +const addToRemoveQueue = (toastId: string) => { + if (toastTimeouts.has(toastId)) { + return; + } + + const timeout = setTimeout(() => { + toastTimeouts.delete(toastId); + dispatch({ + type: "REMOVE_TOAST", + toastId: toastId, + }); + }, TOAST_REMOVE_DELAY); + + toastTimeouts.set(toastId, timeout); +}; + +export const reducer = (state: State, action: Action): State => { + switch (action.type) { + case "ADD_TOAST": + return { + ...state, + toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), + }; + + case "UPDATE_TOAST": + return { + ...state, + toasts: state.toasts.map((t) => + t.id === action.toast.id ? { ...t, ...action.toast } : t, + ), + }; + + case "DISMISS_TOAST": { + const { toastId } = action; + + // ! Side effects ! - This could be extracted into a dismissToast() action, + // but I'll keep it here for simplicity + if (toastId) { + addToRemoveQueue(toastId); + } else { + for (const toast of state.toasts) { + addToRemoveQueue(toast.id); + } + } + + return { + ...state, + toasts: state.toasts.map((t) => + t.id === toastId || toastId === undefined + ? { + ...t, + open: false, + } + : t, + ), + }; + } + case "REMOVE_TOAST": + if (action.toastId === undefined) { + return { + ...state, + toasts: [], + }; + } + + return { + ...state, + toasts: state.toasts.filter((t) => t.id !== action.toastId), + }; + } +}; + +const listeners: Array<(state: State) => void> = []; + +let memoryState: State = { toasts: [] }; + +function dispatch(action: Action) { + memoryState = reducer(memoryState, action); + + for (const listener of listeners) { + listener(memoryState); + } +} + +type Toast = Omit<ToasterToast, "id">; + +function toast({ ...props }: Toast) { + const id = toastGenId(); + + const update = (props: ToasterToast) => { + dispatch({ + type: "UPDATE_TOAST", + toast: { ...props, id }, + }); + }; + + const dismiss = () => { + dispatch({ type: "DISMISS_TOAST", toastId: id }); + }; + + dispatch({ + type: "ADD_TOAST", + toast: { + ...props, + id, + open: true, + onOpenChange: (open) => { + if (!open) { + dismiss(); + } + }, + }, + }); + + return { + id: id, + dismiss, + update, + }; +} + +function useToast() { + const [state, setState] = React.useState<State>(memoryState); + + React.useEffect(() => { + listeners.push(setState); + + return () => { + const index = listeners.indexOf(setState); + + if (index > -1) { + listeners.splice(index, 1); + } + }; + }, [state]); + + return { + ...state, + toast, + dismiss: (toastId?: string) => { + dispatch({ type: "DISMISS_TOAST", toastId }); + }, + }; +} + +export { toast, useToast }; diff --git a/addons/browser/shared/utils/cn.ts b/addons/browser/shared/utils/cn.ts new file mode 100644 index 00000000..91aba1fe --- /dev/null +++ b/addons/browser/shared/utils/cn.ts @@ -0,0 +1,8 @@ +import type { ClassValue } from "clsx"; + +import { clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/addons/browser/shared/utils/files.ts b/addons/browser/shared/utils/files.ts new file mode 100644 index 00000000..f110a040 --- /dev/null +++ b/addons/browser/shared/utils/files.ts @@ -0,0 +1,7 @@ +import { dirname } from "pathe"; +import { fileURLToPath } from "url"; + +// __dirname for ES module context +export function getCurrentDirname(metaUrl: string): string { + return dirname(fileURLToPath(metaUrl)); +} diff --git a/addons/browser/shared/utils/index.ts b/addons/browser/shared/utils/index.ts new file mode 100644 index 00000000..6d05f5fa --- /dev/null +++ b/addons/browser/shared/utils/index.ts @@ -0,0 +1,3 @@ +export * from "./cn"; + +export * from "./files"; diff --git a/addons/browser/shared/utils/math.ts b/addons/browser/shared/utils/math.ts new file mode 100644 index 00000000..90f2fbea --- /dev/null +++ b/addons/browser/shared/utils/math.ts @@ -0,0 +1,15 @@ +import type { ConfigOptions } from "mathjs"; + +import { all, create } from "mathjs"; + +const mathjsConfig: ConfigOptions = { + absTol: 1e-15, + matrix: "Matrix", + number: "number", + precision: 64, + predictable: false, + randomSeed: null, + relTol: 1e-12, +}; + +export const math = create(all || {}, mathjsConfig); diff --git a/addons/cluster/reliverse/components/Onboarding.md b/addons/cluster/reliverse/components/Onboarding.md new file mode 100644 index 00000000..a8221497 --- /dev/null +++ b/addons/cluster/reliverse/components/Onboarding.md @@ -0,0 +1,43 @@ +# Developer Onboarding + +🚀 Yay! Congrats on the installation! If you like it, please [give us a star on GitHub](https://github.com/blefnk/relivator-nextjs-template)! 🙏 + +Welcome to Relivator 1.2.6, a project created by @blefnk Nazar Kornienko. + +**This replacement system is still under development and may not be finalized. Please check back later. For now, you can use this folder to simply store files that you don't want to be overwritten during an upgrade.** + +## Draft Description + +You are reading the `onboard.md` file in the `cluster` folder. You can read more about this folder at the end of this onboarding tutorial. Thank you for installing Relivator! + +## Recommended Steps + +1. **Explore the App**: Click on everything you see in the app to learn how it works. +2. **Read the Documentation**: Go to [Relivator's README.md](../../README.md) and read the latest documentation. +3. **Understand the Cluster Folder**: Read the `The Cluster Folder` section below. Create your own components there or anywhere you prefer. +4. **Join Our Community**: Join the project's <a href="https://discord.gg/Pb8uKbwpsJ">Discord server</a>. +5. **Contribute**: Contribute to the project or create a new issue and grab some cool gifts. + +## The Cluster Folder + +The `cluster` folder is intended for your custom components and files. Storing them here ensures they will not be overwritten when new Relivator updates are released. + +You can also use the `cluster` folder to override the default components and files that come with Relivator. This is useful if you want to make global changes to the default components and files provided by Relivator, but do not want to touch the original files. + +For example, if you want to override the default `Onboarding.tsx` file (originally located in `src\components\Emails\Onboarding.tsx`), you can place an `Onboarding.tsx` file in the `cluster` folder. The `Onboarding.tsx` file in the `cluster` folder will override the default `Onboarding.tsx` file that comes with Relivator. Currently you need manually change paths! 😉 + +The same applies to any other component or file that you want to override. Ensure you replace the paths in the original files with the paths to the `cluster` folder when overriding the original components and files. + +Additionally, you can easily migrate to Relivator by placing specific parts or the entire current React project in this folder and then incrementally adapting it to work with Relivator. + +## Have Any Questions or Issues? + +If you have any questions or issues regarding this project, please contact us: + +- By visiting our <a href="https://discord.gg/Pb8uKbwpsJ">Discord server</a> +- By email: <a href="mailto:blefnk@gmail.com">blefnk@gmail.com</a> +- By creating a <a href="https://github.com/blefnk/relivator-nextjs-template/issues">new issue</a> + +## P.S + +You may not need this folder in the future. We have a great idea for 1.3.x, we have something incredible coming! Stay tuned for more details! diff --git a/addons/cluster/reliverse/components/Onboarding.tsx b/addons/cluster/reliverse/components/Onboarding.tsx new file mode 100644 index 00000000..4c87952a --- /dev/null +++ b/addons/cluster/reliverse/components/Onboarding.tsx @@ -0,0 +1,208 @@ +import Image from "next/image"; + +import { Separator } from "@/browser/reliverse/ui/Separator"; +import { Head } from "@react-email/head"; +import { Html } from "@react-email/html"; +import { Img } from "@react-email/img"; +import { Tailwind } from "@react-email/tailwind"; + +// This replacement system is still under development and may not be finalized. Please check back later. +// For now, you can use this folder to simply store files that you don't want to be overwritten during an upgrade. +export default function Onboarding() { + return ( + <> + <Separator className="mb-2" /> + <article + className={` + prose max-w-screen-lg pb-8 + + dark:prose-invert + + lg:prose-xl + `} + > + <h1 className="mb-4 text-2xl font-bold"> + Welcome to the Cluster Directory Onboarding! + </h1> + <p> + Hello! This is an example custom component in the + 'addons\cluster\reliverse\components\Onboarding.tsx' directory. Please + open 'Onboarding.tsx' and the 'Onboarding.md' file to lean more. + Original file is located at 'src/components/Emails/Onboarding.tsx'. + </p> + {/* <h2 className="mt-6 text-xl font-semibold">Getting Started:</h2> + <ol className="ml-6 mt-2 list-decimal"> + <li> + Open the 'Onboarding.md' file to get an overview of the onboarding + process. Edit 'src/cluster/Onboarding.tsx' file. + </li> + <li> + Follow the instructions in the 'README.md' and 'Onboard.md' file to + complete the initial setup. + </li> + </ol> */} + <h2 className="mb-4 mt-6 text-xl font-semibold"> + README.md RQA (Reliverse Questions Answers) (FAQ): + </h2> + <p className="mb-4 font-bold text-primary/90"> + Reliverse has prepared 30+ interesting questions and answers for you, + like the ones below. Just find the appropriate section in the + README.md file and enjoy! + </p> + <p> + RQ29: Should I modify the components by + [shadcn/ui](https://ui.shadcn.com) (as of Relivator 1.2.6, they are + located in the "addons/browser/reliverse/shadcn/ui" folder)? RA29: You + may lose your changes if @shadcn or + [Reliverse](https://github.com/orgs/reliverse/repositories) update any + of these components in the release of Relivator 1.3.x+. Therefore, the + best option currently is to use, for example, the + "addons/cluster/reliverse/shadcn/ui" folder, where you can have files + that you can safely overwrite the original files with, ensuring you do + not lose your changes. As an example, this folder already contains a + `cluster-readme.tsx` file, which only re-exports the original + `button.tsx` file. So, you can create a `button.tsx` file here and + copy and paste that line into your newly created file. Alternatively, + you can duplicate the code from the original file and make any + modifications you want. Use `Cmd/Ctrl+Shift+H` and simply replace + `addons/browser/reliverse/shadcn/ui` with + `addons/cluster/reliverse/shadcn/ui` (the difference is only in the + words "browser" and "cluster"). `addons/cluster` is your house; feel + free to do anything you want here, mess it up or tidy it up as you + wish. This is your own house, and no one has the right to take it away + from you. + </p> + <br /> + <p> + RA29: You may lose your changes if @shadcn or + [Reliverse](https://github.com/orgs/reliverse/repositories) update any + of these components in the release of Relivator 1.3.x+. Therefore, the + best option currently is to use, for example, the + "addons/cluster/reliverse/shadcn/ui" folder, where you can have files + that you can safely overwrite the original files with, ensuring you do + not lose your changes. As an example, this folder already contains a + `cluster-readme.tsx` file, which only re-exports the original + `button.tsx` file. So, you can create a `button.tsx` file here and + copy and paste that line into your newly created file. Alternatively, + you can duplicate the code from the original file and make any + modifications you want. Use `Cmd/Ctrl+Shift+H` and simply replace + `addons/browser/reliverse/shadcn/ui` with + `addons/cluster/reliverse/shadcn/ui` (the difference is only in the + words "browser" and "cluster"). `addons/cluster` is your house; feel + free to do anything you want here, mess it up or tidy it up as you + wish. This is your own house, and no one has the right to take it away + from you. + </p> + </article> + </> + ); +} + +export function Onboard({ firstName = "FirstName" }) { + return ( + <Html> + <Head /> + <Tailwind> + <Img + alt="Relivator Logo" + src="https://relivator.bleverse.com/logo.png" + /> + <p className="text-2xl">Welcome {firstName}, </p> + <p className="text-lg"> + Ready to revolutionize the web development journey? the decision to + join us at our Relivator introduction event on the "Bleverse Conf: + Spring 2024" is the first step towards mastering Next.js 15 and + beyond. We're excited to unveil the capabilities and innovations of + Relivator to you. + </p> + <ul> + <li> + <span className="font-bold">🚀 Relivator - Next.js Redefined:</span> + <br /> + Dive into the advanced features of Relivator, including its seamless + integration with Stripe, responsive Tailwind design, and powerful + Drizzle ORM for database management. + </li> + <li> + <span className="font-bold"> + 💡 Mastering Modern Web Development: + </span> + <br /> + Learn how Relivator leverages React 19 and Next.js 15 to provide an + enhanced development experience, from server components to advanced + hooks. + </li> + <li> + <span className="font-bold"> + 🌐 Seamless Database and Authentication Integration: + </span> + <br /> + Discover how Relivator simplifies the integration of databases + (MySQL and PostgreSQL) and authentication systems (NextAuth.js and + Clerk), tailoring them to the project’s needs. + </li> + <li> + <span className="font-bold"> + 🎨 Crafting Aesthetic UI with Ease: + </span> + <br /> + Explore the elegant and functional UI components built on top of + Flowbite and Shadcn UI, styled with Radix and Tailwind CSS for a + modern aesthetic. + </li> + </ul> + <h4>Event Details: </h4> + <ul> + <li>Date: [Event Date (TBD)]</li> + <li>Time: [Event Time (TBD)]</li> + <li>Location: [Event Link (TBD)]</li> + <li>Don't miss out, reserve the spot now!</li> + <li>*TBD - To Be Determined</li> + </ul> + <button + className={` + mx-auto w-fit rounded-2xl bg-blue-600 px-6 py-2 text-xl font-bold + text-white + `} + type="button" + > + Join Event + </button> + <p className="text-center">🌟 Join 15 minutes early to network! 🌟</p> + <p> + Embark on a transformative journey with Relivator, Bleverse, and with + our friends and partners. This event is not just an introduction, it's + a gateway to the future of web development. + <br /> + <br /> + We're thrilled to have you with us on this adventure. + <br /> + <br /> + Warm Regards, + <br /> + <br /> + Nazar Kornienko <br /> Reliverse & Bleverse Creator + </p> + </Tailwind> + </Html> + ); +} + +export function OnboardSample({ firstName = "FirstName" }) { + return ( + <div className="mx-auto mt-10 max-w-[500px]"> + <Image + alt="Relivator Logo" + height={150} + src="https://relivator.bleverse.com/logo.png" + width={500} + /> + <a href="/page">asa</a> + <p className="text-2xl">Hello {firstName}, welcome to Relivator!</p> + <p className="text-lg"> + We're excited to introduce you to the world of advanced web development + with Relivator. It's a game-changer! + </p> + </div> + ); +} diff --git a/addons/cluster/reliverse/ui/cluster-readme.tsx b/addons/cluster/reliverse/ui/cluster-readme.tsx new file mode 100644 index 00000000..0d2bc2da --- /dev/null +++ b/addons/cluster/reliverse/ui/cluster-readme.tsx @@ -0,0 +1,6 @@ +// !! export { Button } from "@/browser/reliverse/ui/button"; +// eslint-disable-next-line @stylistic/max-len +// Try not to modify the original shadcn components manually (only @reliverse/addons-relimter codemods scripts are allowed to modify them). If you want to change shadcn components, for example, the `Button`, then do it in this file. This way, you can safely make changes without losing your work when updates are released in the future. If you don't want to change anything about the Button, just leave this re-export as it is. +// ?| README.md: +// RQ29: Should I modify the components by [shadcn/ui](https://ui.shadcn.com) (as of Relivator 1.2.6, they are located in the "addons/browser/reliverse/shadcn/ui" folder)? +// RA29: You may lose your changes if @shadcn or [Reliverse](https://github.com/orgs/reliverse/repositories) update any of these components in the release of Relivator 1.3.x+. Therefore, the best option currently is to use, for example, the "addons/cluster/reliverse/shadcn/ui" folder, where you can have files that you can safely overwrite the original files with, ensuring you do not lose your changes. As an example, this folder already contains a `cluster-readme.tsx` file, which only re-exports the original `button.tsx` file. So, you can create a `button.tsx` file here and copy and paste that line into your newly created file. Alternatively, you can duplicate the code from the original file and make any modifications you want. Use `Cmd/Ctrl+Shift+H` and simply replace `addons/browser/reliverse/shadcn/ui` with `addons/cluster/reliverse/shadcn/ui` (the difference is only in the words "browser" and "cluster"). `addons/cluster` is your house; feel free to do anything you want here, mess it up or tidy it up as you wish. This is your own house, and no one has the right to take it away from you. diff --git a/addons/cluster/temp/![delete-in-1.3.0]!/.gitkeep b/addons/cluster/temp/![delete-in-1.3.0]!/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/addons/cluster/temp/.lintstagedrc.json.txt b/addons/cluster/temp/.lintstagedrc.json.txt new file mode 100644 index 00000000..6e2d7daa --- /dev/null +++ b/addons/cluster/temp/.lintstagedrc.json.txt @@ -0,0 +1,12 @@ +// 🔴 DEPRECATED AND POSSIBLY WILL BE REMOVED IN RELIVATOR 1.3.0 🔴 || +// Starting Relivator 1.3.0, it can be added by using pnpm reliverse || +// ================================================================= || + +{ + "**/*.{js,jsx,cjs,mjs,ts,tsx,md,mdx}": [ + "eslint --fix", + "prettier --check --write" + ], + "**/*.{json,yml}": ["prettier --check --write"], + "**/*.css": ["prettier --write"] +} diff --git a/addons/cluster/temp/.madrun.js.txt b/addons/cluster/temp/.madrun.js.txt new file mode 100644 index 00000000..c6357c74 --- /dev/null +++ b/addons/cluster/temp/.madrun.js.txt @@ -0,0 +1,21 @@ +import { cutEnv, run } from "madrun"; + +const env = { + CI: 1, +}; + +export default { + "env:lint": () => [env, "putout .vscode/presets"], + "fix:lint": async () => + await run("lint", "--fix", { + NODE_ENV: "development", + }), + lint: () => "putout .vscode/presets", + "lint:env": () => [ + "putout .vscode/presets", + { + CI: 1, + }, + ], + "lint:no-env": async () => await cutEnv("lint:env"), +}; diff --git a/addons/cluster/temp/.npmcheckrc.txt b/addons/cluster/temp/.npmcheckrc.txt new file mode 100644 index 00000000..d0571cfb --- /dev/null +++ b/addons/cluster/temp/.npmcheckrc.txt @@ -0,0 +1,38 @@ +// 🔴 DEPRECATED AND POSSIBLY WILL BE REMOVED IN RELIVATOR 1.3.0 🔴 || +// Starting Relivator 1.3.0, it can be added by using pnpm reliverse || +// ================================================================= || + +{ + "depcheck": { + "ignoreMatches": [ + "~", + "@clerk/localizations", + "@clerk/nextjs", + "@clerk/themes", + "@clerk/types", + "@cspell/dict-companies", + "@cspell/dict-de-de", + "@cspell/dict-es-es", + "@cspell/dict-fr-fr", + "@cspell/dict-fullstack", + "@cspell/dict-it-it", + "@cspell/dict-markdown", + "@cspell/dict-npm", + "@cspell/dict-pl_pl", + "@cspell/dict-tr-tr", + "@cspell/dict-typescript", + "@cspell/dict-uk-ua", + "@tanstack/react-query", + "@types/node", + "autoprefixer", + "cspell", + "dotenv", + "flag-icons", + "drizzle.config", + "eslint-interactive", + "international-types", + "next-international", + "postcss" + ] + } +} diff --git a/addons/cluster/temp/.prettierignore.txt b/addons/cluster/temp/.prettierignore.txt new file mode 100644 index 00000000..9ccf128f --- /dev/null +++ b/addons/cluster/temp/.prettierignore.txt @@ -0,0 +1,22 @@ +// 🔴 DEPRECATED AND POSSIBLY WILL BE REMOVED IN RELIVATOR 1.3.0 🔴 || +// Starting Relivator 1.3.0, it can be added by using pnpm reliverse || +// ================================================================= || + +# @see https://prettier.io/docs/en/ignore.html + +# dependencies +/node_modules + +# next.js +/.next +/out + +# production +/build +/dist + +# typescript +next-env.d.ts + +# project +/drizzle diff --git a/addons/cluster/temp/.storybook/main.ts.txt b/addons/cluster/temp/.storybook/main.ts.txt new file mode 100644 index 00000000..90ffc5e4 --- /dev/null +++ b/addons/cluster/temp/.storybook/main.ts.txt @@ -0,0 +1,23 @@ +// 🔴 DEPRECATED AND POSSIBLY WILL BE REMOVED IN RELIVATOR 1.3.0 🔴 || +// Starting Relivator 1.3.0, it can be added by using pnpm reliverse || +// ================================================================= || + +/** + * @see https://github.com/shilman/storybook-rsc-demo + * @see https://storybook.js.org/blog/storybook-react-server-components + */ + +import type { StorybookConfig } from "@storybook/nextjs"; + +const config: StorybookConfig = { + framework: { + name: "@storybook/nextjs", + options: { + /**/ + }, + }, + staticDirs: ["../src/public"], + stories: [], +}; + +export default config; diff --git a/addons/cluster/temp/.swcrc.txt b/addons/cluster/temp/.swcrc.txt new file mode 100644 index 00000000..c8775fbf --- /dev/null +++ b/addons/cluster/temp/.swcrc.txt @@ -0,0 +1,40 @@ +// 🔴 DEPRECATED AND POSSIBLY WILL BE REMOVED IN RELIVATOR 1.3.0 🔴 || +// Starting Relivator 1.3.0, it can be added by using pnpm reliverse || +// ================================================================= || + +{ + "$schema": "https://json.schemastore.org/swcrc", + "minify": true, + "env": { + "mode": "entry", + "coreJs": "3.27", + "targets": { + "chrome": "64", + "edge": "79", + "firefox": "67", + "opera": "51", + "safari": "12", + "ios": "12" + } + }, + "jsc": { + "transform": { "react": { "runtime": "automatic" } }, + "parser": { + "jsx": false, + "decoratorsBeforeExport": false, + "exportNamespaceFrom": false, + "exportDefaultFrom": false, + "dynamicImport": false, + "decorators": false, + "importMeta": false, + "functionBind": false, + "privateMethod": false, + "syntax": "ecmascript", + "topLevelAwait": false + }, + "externalHelpers": true, + "keepClassNames": false, + "target": "esnext", + "loose": true + } +} diff --git a/addons/cluster/temp/.thing/WT.md.txt b/addons/cluster/temp/.thing/WT.md.txt new file mode 100644 index 00000000..173f6a47 --- /dev/null +++ b/addons/cluster/temp/.thing/WT.md.txt @@ -0,0 +1,7 @@ +# webhookthing + +[webhookthing](https://docs.webhookthing.com): Run webhooks locally with 1 click. + +// 🔴 DEPRECATED AND POSSIBLY WILL BE REMOVED IN RELIVATOR 1.3.0 🔴 || +// Starting Relivator 1.3.0, it can be added by using pnpm reliverse || +// ================================================================= || diff --git a/addons/cluster/temp/.thing/hooks/discord_webhook.json.txt b/addons/cluster/temp/.thing/hooks/discord_webhook.json.txt new file mode 100644 index 00000000..e3fbf6c7 --- /dev/null +++ b/addons/cluster/temp/.thing/hooks/discord_webhook.json.txt @@ -0,0 +1,35 @@ +{ + "content": "Hey, welcome to webhookthing The easiest way to test webhooks on the local dev environment!\n_ _", + "embeds": [ + { + "title": "What's this?", + "description": "This is a sample discord embed", + "color": 5814783, + "fields": [ + { + "name": "This is a field", + "value": "This is the field's value" + } + ], + "author": { + "name": "This is the Author", + "url": "https://example.com", + "icon_url": "https://via.placeholder.com/64" + }, + "footer": { + "text": "This is a footer", + "icon_url": "https://via.placeholder.com/8" + }, + "timestamp": "1970-01-01T08:00:00.000Z", + "image": { + "url": "https://via.placeholder.com/512" + }, + "thumbnail": { + "url": "https://via.placeholder.com/64" + } + } + ], + "username": "example#0001", + "avatar_url": "https://via.placeholder.com/32", + "attachments": [] +} diff --git a/addons/cluster/temp/.thing/hooks/github_pr_opened.json.txt b/addons/cluster/temp/.thing/hooks/github_pr_opened.json.txt new file mode 100644 index 00000000..96448f99 --- /dev/null +++ b/addons/cluster/temp/.thing/hooks/github_pr_opened.json.txt @@ -0,0 +1,532 @@ +{ + "action": "opened", + "number": 15, + "pull_request": { + "url": "https://api.github.com/repos/example_org/example_repo/pulls/15", + "id": 1211243938, + "node_id": "PR_0000000000000000", + "html_url": "https://github.com/example_org/example_repo/pull/15", + "diff_url": "https://github.com/example_org/example_repo/pull/15.diff", + "patch_url": "https://github.com/example_org/example_repo/pull/15.patch", + "issue_url": "https://api.github.com/repos/example_org/example_repo/issues/15", + "number": 15, + "state": "open", + "locked": false, + "title": "jdoe/example-branch", + "user": { + "login": "jdoe", + "id": 1234567, + "node_id": "MDQ6VXNlcjEyMzQ1Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/1234567?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/jdoe", + "html_url": "https://github.com/jdoe", + "followers_url": "https://api.github.com/users/jdoe/followers", + "following_url": "https://api.github.com/users/jdoe/following{/other_user}", + "gists_url": "https://api.github.com/users/jdoe/gists{/gist_id}", + "starred_url": "https://api.github.com/users/jdoe/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/jdoe/subscriptions", + "organizations_url": "https://api.github.com/users/jdoe/orgs", + "repos_url": "https://api.github.com/users/jdoe/repos", + "events_url": "https://api.github.com/users/jdoe/events{/privacy}", + "received_events_url": "https://api.github.com/users/jdoe/received_events", + "type": "User", + "site_admin": false + }, + "body": "This is a test PR", + "created_at": "2023-01-20T09:03:04Z", + "updated_at": "2023-01-20T09:03:04Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": null, + "assignee": null, + "assignees": [], + "requested_reviewers": [], + "requested_teams": [], + "labels": [], + "milestone": null, + "draft": false, + "commits_url": "https://api.github.com/repos/example_org/example_repo/pulls/15/commits", + "review_comments_url": "https://api.github.com/repos/example_org/example_repo/pulls/15/comments", + "review_comment_url": "https://api.github.com/repos/example_org/example_repo/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/example_org/example_repo/issues/15/comments", + "statuses_url": "https://api.github.com/repos/example_org/example_repo/statuses/07a6048532c799c58bf7eafdbc7d4eaf6b6bbde6", + "head": { + "label": "example_org:jdoe/example-branch", + "ref": "jdoe/example-branch", + "sha": "07a6048532c799c58bf7eafdbc7d4eaf6b6bbde6", + "user": { + "login": "example_org", + "id": 2345678, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjIzNDU2Nzg=", + "avatar_url": "https://avatars.githubusercontent.com/u/2345678?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/example_org", + "html_url": "https://github.com/example_org", + "followers_url": "https://api.github.com/users/example_org/followers", + "following_url": "https://api.github.com/users/example_org/following{/other_user}", + "gists_url": "https://api.github.com/users/example_org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/example_org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/example_org/subscriptions", + "organizations_url": "https://api.github.com/users/example_org/orgs", + "repos_url": "https://api.github.com/users/example_org/repos", + "events_url": "https://api.github.com/users/example_org/events{/privacy}", + "received_events_url": "https://api.github.com/users/example_org/received_events", + "type": "Organization", + "site_admin": false + }, + "repo": { + "id": 553972582, + "node_id": "R_kgDOIQTzZg", + "name": "example_repo", + "full_name": "example_org/example_repo", + "private": true, + "owner": { + "login": "example_org", + "id": 2345678, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjIzNDU2Nzg=", + "avatar_url": "https://avatars.githubusercontent.com/u/2345678?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/example_org", + "html_url": "https://github.com/example_org", + "followers_url": "https://api.github.com/users/example_org/followers", + "following_url": "https://api.github.com/users/example_org/following{/other_user}", + "gists_url": "https://api.github.com/users/example_org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/example_org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/example_org/subscriptions", + "organizations_url": "https://api.github.com/users/example_org/orgs", + "repos_url": "https://api.github.com/users/example_org/repos", + "events_url": "https://api.github.com/users/example_org/events{/privacy}", + "received_events_url": "https://api.github.com/users/example_org/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/example_org/example_repo", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/example_org/example_repo", + "forks_url": "https://api.github.com/repos/example_org/example_repo/forks", + "keys_url": "https://api.github.com/repos/example_org/example_repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/example_org/example_repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/example_org/example_repo/teams", + "hooks_url": "https://api.github.com/repos/example_org/example_repo/hooks", + "issue_events_url": "https://api.github.com/repos/example_org/example_repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/example_org/example_repo/events", + "assignees_url": "https://api.github.com/repos/example_org/example_repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/example_org/example_repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/example_org/example_repo/tags", + "blobs_url": "https://api.github.com/repos/example_org/example_repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/example_org/example_repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/example_org/example_repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/example_org/example_repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/example_org/example_repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/example_org/example_repo/languages", + "stargazers_url": "https://api.github.com/repos/example_org/example_repo/stargazers", + "contributors_url": "https://api.github.com/repos/example_org/example_repo/contributors", + "subscribers_url": "https://api.github.com/repos/example_org/example_repo/subscribers", + "subscription_url": "https://api.github.com/repos/example_org/example_repo/subscription", + "commits_url": "https://api.github.com/repos/example_org/example_repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/example_org/example_repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/example_org/example_repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/example_org/example_repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/example_org/example_repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/example_org/example_repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/example_org/example_repo/merges", + "archive_url": "https://api.github.com/repos/example_org/example_repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/example_org/example_repo/downloads", + "issues_url": "https://api.github.com/repos/example_org/example_repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/example_org/example_repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/example_org/example_repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/example_org/example_repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/example_org/example_repo/labels{/name}", + "releases_url": "https://api.github.com/repos/example_org/example_repo/releases{/id}", + "deployments_url": "https://api.github.com/repos/example_org/example_repo/deployments", + "created_at": "2022-10-19T03:41:52Z", + "updated_at": "2022-10-23T23:12:34Z", + "pushed_at": "2023-01-20T09:03:04Z", + "git_url": "git://github.com/example_org/example_repo.git", + "ssh_url": "git@github.com:example_org/example_repo.git", + "clone_url": "https://github.com/example_org/example_repo.git", + "svn_url": "https://github.com/example_org/example_repo", + "homepage": "https://example.com", + "size": 642, + "stargazers_count": 1, + "watchers_count": 1, + "language": "TypeScript", + "has_issues": true, + "has_projects": false, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 4, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "allow_forking": false, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "internal", + "forks": 0, + "open_issues": 4, + "watchers": 1, + "default_branch": "main", + "allow_squash_merge": true, + "allow_merge_commit": false, + "allow_rebase_merge": false, + "allow_auto_merge": true, + "delete_branch_on_merge": false, + "allow_update_branch": true, + "use_squash_pr_title_as_default": false, + "squash_merge_commit_message": "COMMIT_MESSAGES", + "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE" + } + }, + "base": { + "label": "example_org:main", + "ref": "main", + "sha": "caf87bf0162986f2874ec1b668f1d576b9f99e76", + "user": { + "login": "example_org", + "id": 2345678, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjIzNDU2Nzg=", + "avatar_url": "https://avatars.githubusercontent.com/u/2345678?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/example_org", + "html_url": "https://github.com/example_org", + "followers_url": "https://api.github.com/users/example_org/followers", + "following_url": "https://api.github.com/users/example_org/following{/other_user}", + "gists_url": "https://api.github.com/users/example_org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/example_org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/example_org/subscriptions", + "organizations_url": "https://api.github.com/users/example_org/orgs", + "repos_url": "https://api.github.com/users/example_org/repos", + "events_url": "https://api.github.com/users/example_org/events{/privacy}", + "received_events_url": "https://api.github.com/users/example_org/received_events", + "type": "Organization", + "site_admin": false + }, + "repo": { + "id": 553972582, + "node_id": "R_kgDOIQTzZg", + "name": "captain", + "full_name": "example_org/example_repo", + "private": true, + "owner": { + "login": "example_org", + "id": 2345678, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjIzNDU2Nzg=", + "avatar_url": "https://avatars.githubusercontent.com/u/2345678?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/example_org", + "html_url": "https://github.com/example_org", + "followers_url": "https://api.github.com/users/example_org/followers", + "following_url": "https://api.github.com/users/example_org/following{/other_user}", + "gists_url": "https://api.github.com/users/example_org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/example_org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/example_org/subscriptions", + "organizations_url": "https://api.github.com/users/example_org/orgs", + "repos_url": "https://api.github.com/users/example_org/repos", + "events_url": "https://api.github.com/users/example_org/events{/privacy}", + "received_events_url": "https://api.github.com/users/example_org/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/example_org/example_repo", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/example_org/example_repo", + "forks_url": "https://api.github.com/repos/example_org/example_repo/forks", + "keys_url": "https://api.github.com/repos/example_org/example_repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/example_org/example_repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/example_org/example_repo/teams", + "hooks_url": "https://api.github.com/repos/example_org/example_repo/hooks", + "issue_events_url": "https://api.github.com/repos/example_org/example_repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/example_org/example_repo/events", + "assignees_url": "https://api.github.com/repos/example_org/example_repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/example_org/example_repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/example_org/example_repo/tags", + "blobs_url": "https://api.github.com/repos/example_org/example_repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/example_org/example_repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/example_org/example_repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/example_org/example_repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/example_org/example_repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/example_org/example_repo/languages", + "stargazers_url": "https://api.github.com/repos/example_org/example_repo/stargazers", + "contributors_url": "https://api.github.com/repos/example_org/example_repo/contributors", + "subscribers_url": "https://api.github.com/repos/example_org/example_repo/subscribers", + "subscription_url": "https://api.github.com/repos/example_org/example_repo/subscription", + "commits_url": "https://api.github.com/repos/example_org/example_repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/example_org/example_repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/example_org/example_repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/example_org/example_repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/example_org/example_repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/example_org/example_repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/example_org/example_repo/merges", + "archive_url": "https://api.github.com/repos/example_org/example_repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/example_org/example_repo/downloads", + "issues_url": "https://api.github.com/repos/example_org/example_repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/example_org/example_repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/example_org/example_repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/example_org/example_repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/example_org/example_repo/labels{/name}", + "releases_url": "https://api.github.com/repos/example_org/example_repo/releases{/id}", + "deployments_url": "https://api.github.com/repos/example_org/example_repo/deployments", + "created_at": "2022-10-19T03:41:52Z", + "updated_at": "2022-10-23T23:12:34Z", + "pushed_at": "2023-01-20T09:03:04Z", + "git_url": "git://github.com/example_org/example_repo.git", + "ssh_url": "git@github.com:example_org/example_repo.git", + "clone_url": "https://github.com/example_org/example_repo.git", + "svn_url": "https://github.com/example_org/example_repo", + "homepage": "https://example.com", + "size": 642, + "stargazers_count": 1, + "watchers_count": 1, + "language": "TypeScript", + "has_issues": true, + "has_projects": false, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 4, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "allow_forking": false, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "internal", + "forks": 0, + "open_issues": 4, + "watchers": 1, + "default_branch": "main", + "allow_squash_merge": true, + "allow_merge_commit": false, + "allow_rebase_merge": false, + "allow_auto_merge": true, + "delete_branch_on_merge": false, + "allow_update_branch": true, + "use_squash_pr_title_as_default": false, + "squash_merge_commit_message": "COMMIT_MESSAGES", + "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/example_org/example_repo/pulls/15" + }, + "html": { + "href": "https://github.com/example_org/example_repo/pull/15" + }, + "issue": { + "href": "https://api.github.com/repos/example_org/example_repo/issues/15" + }, + "comments": { + "href": "https://api.github.com/repos/example_org/example_repo/issues/15/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/example_org/example_repo/pulls/15/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/example_org/example_repo/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/example_org/example_repo/pulls/15/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/example_org/example_repo/statuses/07a6048532c799c58bf7eafdbc7d4eaf6b6bbde6" + } + }, + "author_association": "COLLABORATOR", + "auto_merge": null, + "active_lock_reason": null, + "merged": false, + "mergeable": null, + "rebaseable": null, + "mergeable_state": "unknown", + "merged_by": null, + "comments": 0, + "review_comments": 0, + "maintainer_can_modify": false, + "commits": 7, + "additions": 557, + "deletions": 45, + "changed_files": 27 + }, + "repository": { + "id": 553972582, + "node_id": "R_kgDOIQTzZg", + "name": "captain", + "full_name": "example_org/example_repo", + "private": true, + "owner": { + "login": "example_org", + "id": 2345678, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjIzNDU2Nzg=", + "avatar_url": "https://avatars.githubusercontent.com/u/2345678?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/example_org", + "html_url": "https://github.com/example_org", + "followers_url": "https://api.github.com/users/example_org/followers", + "following_url": "https://api.github.com/users/example_org/following{/other_user}", + "gists_url": "https://api.github.com/users/example_org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/example_org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/example_org/subscriptions", + "organizations_url": "https://api.github.com/users/example_org/orgs", + "repos_url": "https://api.github.com/users/example_org/repos", + "events_url": "https://api.github.com/users/example_org/events{/privacy}", + "received_events_url": "https://api.github.com/users/example_org/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/example_org/example_repo", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/example_org/example_repo", + "forks_url": "https://api.github.com/repos/example_org/example_repo/forks", + "keys_url": "https://api.github.com/repos/example_org/example_repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/example_org/example_repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/example_org/example_repo/teams", + "hooks_url": "https://api.github.com/repos/example_org/example_repo/hooks", + "issue_events_url": "https://api.github.com/repos/example_org/example_repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/example_org/example_repo/events", + "assignees_url": "https://api.github.com/repos/example_org/example_repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/example_org/example_repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/example_org/example_repo/tags", + "blobs_url": "https://api.github.com/repos/example_org/example_repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/example_org/example_repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/example_org/example_repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/example_org/example_repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/example_org/example_repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/example_org/example_repo/languages", + "stargazers_url": "https://api.github.com/repos/example_org/example_repo/stargazers", + "contributors_url": "https://api.github.com/repos/example_org/example_repo/contributors", + "subscribers_url": "https://api.github.com/repos/example_org/example_repo/subscribers", + "subscription_url": "https://api.github.com/repos/example_org/example_repo/subscription", + "commits_url": "https://api.github.com/repos/example_org/example_repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/example_org/example_repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/example_org/example_repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/example_org/example_repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/example_org/example_repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/example_org/example_repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/example_org/example_repo/merges", + "archive_url": "https://api.github.com/repos/example_org/example_repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/example_org/example_repo/downloads", + "issues_url": "https://api.github.com/repos/example_org/example_repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/example_org/example_repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/example_org/example_repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/example_org/example_repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/example_org/example_repo/labels{/name}", + "releases_url": "https://api.github.com/repos/example_org/example_repo/releases{/id}", + "deployments_url": "https://api.github.com/repos/example_org/example_repo/deployments", + "created_at": "2022-10-19T03:41:52Z", + "updated_at": "2022-10-23T23:12:34Z", + "pushed_at": "2023-01-20T09:03:04Z", + "git_url": "git://github.com/example_org/example_repo.git", + "ssh_url": "git@github.com:example_org/example_repo.git", + "clone_url": "https://github.com/example_org/example_repo.git", + "svn_url": "https://github.com/example_org/example_repo", + "homepage": "https://example.com", + "size": 642, + "stargazers_count": 1, + "watchers_count": 1, + "language": "TypeScript", + "has_issues": true, + "has_projects": false, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 4, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "allow_forking": false, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "internal", + "forks": 0, + "open_issues": 4, + "watchers": 1, + "default_branch": "main" + }, + "organization": { + "login": "example_org", + "id": 2345678, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjIzNDU2Nzg=", + "url": "https://api.github.com/orgs/example_org", + "repos_url": "https://api.github.com/orgs/example_org/repos", + "events_url": "https://api.github.com/orgs/example_org/events", + "hooks_url": "https://api.github.com/orgs/example_org/hooks", + "issues_url": "https://api.github.com/orgs/example_org/issues", + "members_url": "https://api.github.com/orgs/example_org/members{/member}", + "public_members_url": "https://api.github.com/orgs/example_org/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/2345678?v=4", + "description": "Example organization description" + }, + "enterprise": { + "id": 12345, + "slug": "example-enterprise", + "name": "Example Enterprise", + "node_id": "E_kgDRKO7", + "avatar_url": "https://avatars.githubusercontent.com/b/12345?v=4", + "description": "Example enterprise description", + "website_url": "https://example.com", + "html_url": "https://github.com/enterprises/example-enterprise", + "created_at": "2022-09-19T23:04:52Z", + "updated_at": "2022-09-19T23:16:12Z" + }, + "sender": { + "login": "jdoe", + "id": 1234567, + "node_id": "MDQ6VXNlcjY3NTE3ODc=", + "avatar_url": "https://avatars.githubusercontent.com/u/1234567?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/jdoe", + "html_url": "https://github.com/jdoe", + "followers_url": "https://api.github.com/users/jdoe/followers", + "following_url": "https://api.github.com/users/jdoe/following{/other_user}", + "gists_url": "https://api.github.com/users/jdoe/gists{/gist_id}", + "starred_url": "https://api.github.com/users/jdoe/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/jdoe/subscriptions", + "organizations_url": "https://api.github.com/users/jdoe/orgs", + "repos_url": "https://api.github.com/users/jdoe/repos", + "events_url": "https://api.github.com/users/jdoe/events{/privacy}", + "received_events_url": "https://api.github.com/users/jdoe/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/.thing/hooks/stripe_payment_failed.json b/addons/cluster/temp/.thing/hooks/stripe_payment_failed.json.txt similarity index 100% rename from .thing/hooks/stripe_payment_failed.json rename to addons/cluster/temp/.thing/hooks/stripe_payment_failed.json.txt diff --git a/.thing/hooks/stripe_payment_succeeded.json b/addons/cluster/temp/.thing/hooks/stripe_payment_succeeded.json.txt similarity index 100% rename from .thing/hooks/stripe_payment_succeeded.json rename to addons/cluster/temp/.thing/hooks/stripe_payment_succeeded.json.txt diff --git a/addons/cluster/temp/.tokenami/tokenami.config.ts.txt b/addons/cluster/temp/.tokenami/tokenami.config.ts.txt new file mode 100644 index 00000000..c91bb31d --- /dev/null +++ b/addons/cluster/temp/.tokenami/tokenami.config.ts.txt @@ -0,0 +1,73 @@ +// 🔴 DEPRECATED AND POSSIBLY WILL BE REMOVED IN RELIVATOR 1.3.0 🔴 || +// Starting Relivator 1.3.0, it can be added by using pnpm reliverse || +// ================================================================= || + +/** + * TODO: See #33 and #90 of the Relivator's Roadmap + * @see https://github.com/tokenami/tokenami#readme + */ + +import { createConfig } from "@tokenami/dev"; + +export default createConfig({ + grid: "0.25rem", + include: ["./src/**/*.{js,jsx,ts,tsx}"], + responsive: { + medium: "@media (min-width: 1024px)", + "medium-self": "@container (min-width: 400px)", + }, + theme: { + alpha: { + /**/ + }, + anim: { + /**/ + }, + border: { + /**/ + }, + color: { + "sky-500": "#0ea5e9", + "slate-100": "#f1f5f9", + "slate-700": "#334155", + }, + ease: { + /**/ + }, + "font-size": { + /**/ + }, + leading: { + /**/ + }, + "line-style": { + /**/ + }, + radii: { + circle: "9999px", + none: "none", + rounded: "10px", + }, + shadow: { + /**/ + }, + size: { + /**/ + }, + surface: { + /**/ + }, + tracking: { + /**/ + }, + transition: { + /**/ + }, + weight: { + /**/ + }, + z: { + /**/ + }, + }, +}); diff --git a/.tokenami/tokenami.env.ci.d.ts b/addons/cluster/temp/.tokenami/tokenami.env.ci.d.ts.txt similarity index 100% rename from .tokenami/tokenami.env.ci.d.ts rename to addons/cluster/temp/.tokenami/tokenami.env.ci.d.ts.txt diff --git a/addons/cluster/temp/.tokenami/tokenami.env.d.ts.txt b/addons/cluster/temp/.tokenami/tokenami.env.d.ts.txt new file mode 100644 index 00000000..802ca793 --- /dev/null +++ b/addons/cluster/temp/.tokenami/tokenami.env.d.ts.txt @@ -0,0 +1,21 @@ +/** + * TODO: See #33 and #90 of the Relivator's Roadmap + * @see https://github.com/tokenami/tokenami#readme + */ + +import type config from "./tokenami.config"; +import type { TokenamiProperties } from "@tokenami/dev"; + +export type Config = typeof config; + +declare module "@tokenami/dev" { + type TokenamiConfig = { + /**/ + } & Config; +} + +declare module "react" { + type CSSProperties = { + /**/ + } & TokenamiProperties; +} diff --git a/addons/cluster/temp/CartSheetClientEdition.tsx.txt b/addons/cluster/temp/CartSheetClientEdition.tsx.txt new file mode 100644 index 00000000..92a7534c --- /dev/null +++ b/addons/cluster/temp/CartSheetClientEdition.tsx.txt @@ -0,0 +1,184 @@ +"use client"; + +import { useEffect, useState } from "react"; + +import Link from "next/link"; +import { useTranslations } from "next-intl"; + +import { CartLineItems } from "~/components/Checkout/CartLineItems"; +import { Icons } from "~/components/Common/Icons"; +import { Badge } from "~/components/Primitives"; +import { Separator } from "~/components/Primitives"; +import { + Sheet, + SheetContent, + SheetFooter, + SheetHeader, + SheetTitle, + SheetTrigger, +} from "~/components/Primitives"; +import { Button } from "~/components/Primitives/ui/Button"; +import { buttonVariants } from "~/components/Primitives"; +import { getCartAction } from "@/server/reliverse/actions/cart"; +import { cn, formatPrice } from "~/utils"; + +type CartItem = { + id: number; + storeId: number; + name: string; + category: "accessories" | "clothing" | "furniture" | "tech"; + images?: { id: string; name: string; url: string }[] | null; + inventory: number; + price: string; + quantity: number; + storeName?: null | string; + subcategory?: string; +}; + +export default function CartSheet() { + const t = useTranslations(); + const [cartLineItems, setCartLineItems] = useState<CartItem[]>([]); + const [itemCount, setItemCount] = useState(0); + const [cartTotal, setCartTotal] = useState(0); + + useEffect(() => { + const fetchCartData = async () => { + try { + const cartItems = await getCartAction(); + + setCartLineItems(cartItems); + + const itemCount = cartItems.reduce( + (total, item) => total + Number(item.quantity) || 0, + 0, + ); + + const cartTotal = cartItems.reduce( + (total, item) => total + item.quantity * Number(item.price) || 0, + 0, + ); + + setItemCount(itemCount); + setCartTotal(cartTotal); + } catch (error) { + setItemCount(0); + setCartTotal(0); + } + }; + + fetchCartData(); + }, []); + + return ( + <Sheet> + <SheetTrigger asChild> + <Button + aria-label="Open cart" + className={` + relative + + ${itemCount > 0 ? "border border-primary/40" : ""} + `} + size="icon" + variant="outline" + > + {itemCount > 0 && ( + <Badge + className={` + absolute -right-2 -top-2 size-6 justify-center rounded-full + border-4 border-primary/20 p-2.5 text-sm text-primary/70 + `} + variant="secondary" + > + {itemCount} + </Badge> + )} + <Icons.cart aria-hidden="true" className="size-4" /> + </Button> + </SheetTrigger> + <SheetContent + className={` + flex w-full flex-col pr-0 + + sm:max-w-lg + `} + > + <SheetHeader className="space-y-2.5 pr-6"> + <SheetTitle> + {t("checkout.cart")} {itemCount > 0 && `(${itemCount})`} + </SheetTitle> + <Separator /> + </SheetHeader> + {itemCount > 0 ? ( + <> + <CartLineItems className="flex-1" items={cartLineItems} /> + <div className="space-y-4 pr-6"> + <Separator /> + <div className="space-y-1.5 text-sm"> + <div className="flex"> + <span className="flex-1">{t("checkout.shipping")}</span> + <span>{t("checkout.free")}</span> + </div> + <div className="flex"> + <span className="flex-1">{t("checkout.taxes")}</span> + <span>{t("checkout.calculated")}</span> + </div> + <div className="flex"> + <span className="flex-1">{t("checkout.total")}</span> + <span>{formatPrice(cartTotal.toFixed(2))}</span> + </div> + </div> + <SheetFooter> + <SheetTrigger> + <Link + aria-label={t("checkout.viewTheCart")} + className={buttonVariants({ + className: "w-full", + size: "sm", + })} + href="/cart" + > + {t("checkout.viewTheCart")} + </Link> + </SheetTrigger> + </SheetFooter> + </div> + </> + ) : ( + <div + className={` + flex h-full flex-col items-center justify-center space-y-1 + `} + > + <Icons.cart + aria-hidden="true" + className="mb-4 size-16 text-muted-foreground" + /> + <div + className={` + text-xl font-medium text-muted-foreground + `} + > + {t("checkout.cartIsEmpty")} + </div> + <SheetTrigger> + <Link + aria-label={t("checkout.continueShopping")} + className={cn( + buttonVariants({ + className: "text-sm text-muted-foreground", + size: "sm", + variant: "link", + }), + )} + href="/products" + > + {t("checkout.continueShopping")} + </Link> + </SheetTrigger> + </div> + )} + </SheetContent> + </Sheet> + ); +} diff --git a/addons/cluster/temp/__middleware.ts.txt b/addons/cluster/temp/__middleware.ts.txt new file mode 100644 index 00000000..ec1b3f8d --- /dev/null +++ b/addons/cluster/temp/__middleware.ts.txt @@ -0,0 +1,70 @@ +import type { + MiddlewareConfig, + NextFetchEvent, + NextRequest, +} from "next/server"; + +import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server"; +import createMiddleware from "next-intl/middleware"; +import { authProvider } from "reliverse.config"; + +const intlMiddleware = createMiddleware({ + defaultLocale: "en", + localePrefix: "always", + locales: ["de", "en", "es", "fa", "fr", "hi", "it", "pl", "tr", "uk", "zh"], +}); + +const isProtectedRoute = createRouteMatcher([ + "/dashboard(.*)", + "/:locale/dashboard(.*)", + "/admin(.*)", + "/:locale/admin(.*)", +]); + +// @see https://nextjs.org/docs/app/building-the-application/routing/middleware +export default function middleware(req: NextRequest, event: NextFetchEvent) { + // Process auth middleware only on auth pages and on protected routes. + if ( + req.nextUrl.pathname.includes("/auth/sign-in") || + req.nextUrl.pathname.includes("/auth/sign-up") || + isProtectedRoute(req) + ) { + if (authProvider === "clerk") { + return clerkMiddleware((auth, req) => { + if (isProtectedRoute(req)) { + const locale = + req.nextUrl.pathname.match(/(\/.*)\/dashboard/)?.at(1) ?? ""; + + const signInUrl = new URL(`${locale}/auth/sign-in`, req.url); + + auth().protect({ + // unauthenticatedUrl fixes error - Unable to find next-intl + // locale because the middleware didn't run on this request. + unauthenticatedUrl: signInUrl.toString(), + }); + } + + return intlMiddleware(req); + })(req, event); + } + + // If authProvider is "authjs" or not provided. + return intlMiddleware(req); + } + + // Process only intl middleware on all other routes. + return intlMiddleware(req); +} + +export const config: MiddlewareConfig = { + // Don't run middleware.ts on any file extension + // or _next paths, match the root and api/trpc + // matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"], // Alternative matcher for more control: + matcher: [ + "/", + "/(de|en|es|fa|fr|hi|it|pl|tr|uk|zh)/:path*", + "/((?!api|_next/static|_next/image|_vercel|favicon\\.ico|.*\\.(?:png|jpg|jpeg|gif|webp|svg)$).*)", + "/unauthorized(.*)", + "/auth/(.*)", + ], +}; diff --git a/addons/cluster/temp/__trpc.ts.txt b/addons/cluster/temp/__trpc.ts.txt new file mode 100644 index 00000000..86da25a6 --- /dev/null +++ b/addons/cluster/temp/__trpc.ts.txt @@ -0,0 +1,177 @@ +import { cache } from "react"; + +import { unstable_cache } from "next/cache"; + +import type { ConfigOptions } from "mathjs"; + +import { initTRPC, TRPCError } from "@trpc/server"; +import { all, create } from "mathjs"; +import { z } from "zod"; + +import { auth } from "~/auth"; +import { db } from "~/db"; + +const mathjsConfig: ConfigOptions = { + absTol: 1e-15, + matrix: "Matrix", + number: "number", + precision: 64, + predictable: false, + randomSeed: null, + relTol: 1e-12, +}; + +const math = create(all || {}, mathjsConfig); + +type Meta = { + span: string; +}; + +// Initializing tRPC with meta configuration +const t = initTRPC.meta<Meta>().create(); + +// Creating context for procedures +const createContext = cache(async () => { + const session = await auth(); + + // const log = createLogger('trpc').child({ + // user: session?.user, + // }) + const log = console; + + return { + log, + user: session, + }; +}); + +// Base procedure configuration with tracing & logging +// @see https://github.com/juliusmarminge/trellix-trpc +// @see https://github.com/baselime/node-opentelemetry/blob/main/TRPC.md +const nextProc = t.procedure // .use(tracing({ collectInput: true, collectRes: true })) + .use(async (options) => { + const context = await createContext(); + + // const input = await opts.getRawInput() + // const log = ctx.log.child({ input }) + const { log } = context; + + if (t._config.isDev) { + // artificial delay in dev + const waitMs = math.floor(math.random() * 400) + 100; + + await new Promise((resolve) => setTimeout(resolve, waitMs)); + } + + const start = Date.now(); + + const res = await options.next({ + ctx: { + ...context, + log, + }, + }); + + const duration = Date.now() - start; + + if (res.ok) { + log.info({ + duration, + res: res.data, + }); + } else { + log.error({ + duration, + error: res.error, + }); + } + + return res; + }) + // @ts-expect-error TODO: fix + .experimental_caller( + // @ts-expect-error TODO: fix + experimental_nextAppDirCaller({ + // @ts-expect-error TODO: fix + pathExtractor: ({ meta }) => meta as Meta["span"], + }), + ); + +// Public procedure +export const publicAction = nextProc; + +// Protected procedure with user authentication +export const protectedAction = nextProc.use( + // @ts-expect-error TODO: fix + async (options) => { + const user = await auth(); + + if (!user) { + throw new TRPCError({ + code: "UNAUTHORIZED", + }); + } + + return options.next({ + ctx: { + ...options.ctx, + user, + }, + }); + }, + + // ensures type is non-nullable +); + +// Procedure with board access validation +export const protectedBoardAction = protectedAction + .input( + z.object({ + boardId: z.string(), + }), + ) + // @ts-expect-error TODO: fix + .use(async (options) => { + const board = await db.query.boards.findFirst({ + where: (fields, ops) => + ops.and( + ops.eq(fields.ownerId, options.ctx.user.id), + ops.eq(fields.id, options.input.boardId), + ), + }); + + if (!board) { + throw new TRPCError({ + code: "FORBIDDEN", + }); + } + + return options.next({ + ctx: { + board, + }, + }); + }); + +// Caching layer for protected actions +export const cachedDataLayer = (cacheTag: string) => + // @ts-expect-error TODO: fix + protectedAction.use(async (options) => + unstable_cache( + async () => { + const res = await options.next(); + + if (!res.ok) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + }); + } + + return res; + }, // should maybe make sure this is serializable + [options.ctx.user.id], + { + tags: [cacheTag], + }, + )(), + ); diff --git a/addons/cluster/temp/academy-crypto.ts.txt b/addons/cluster/temp/academy-crypto.ts.txt new file mode 100644 index 00000000..ebc95522 --- /dev/null +++ b/addons/cluster/temp/academy-crypto.ts.txt @@ -0,0 +1,59 @@ +import crypto from "crypto"; + +import dotenv from "dotenv"; + +dotenv.config(); + +const hashAlgorithm = "sha256"; +const encryptionKey = "ThisIsTheDefaultString32CharLong"; + +// TODO: for the consideration +// const encryptionKey = +// process.env.ENCRYPT_DECRYPT_KEY && +// process.env.ENCRYPT_DECRYPT_KEY.length === 32 +// ? process.env.ENCRYPT_DECRYPT_KEY +// : defaultEncryptDecryptKey; + +export const hashAnswer = (answer: string): string => { + return crypto.createHash(hashAlgorithm).update(answer).digest("base64"); +}; + +export const encryptAnswer = (answer: string): string => { + const iv = crypto.randomBytes(16); + const cipher = crypto.createCipheriv( + "aes-256-cbc", + Buffer.from(encryptionKey), + iv, + ); + + const encrypted = Buffer.concat([ + cipher.update(answer, "utf8"), + cipher.final(), + ]); + + return `${iv.toString("hex")}:${encrypted.toString("hex")}`; +}; + +export const decryptAnswer = (encrypted: string): string => { + const [ivHex, encryptedHex] = encrypted.split(":"); + + if (!ivHex || !encryptedHex) { + throw new Error("Invalid encrypted answer format"); + } + + const iv = Buffer.from(ivHex, "hex"); + const encryptedText = Buffer.from(encryptedHex, "hex"); + + const decipher = crypto.createDecipheriv( + "aes-256-cbc", + Buffer.from(encryptionKey), + iv, + ); + + const decrypted = Buffer.concat([ + decipher.update(encryptedText), + decipher.final(), + ]); + + return decrypted.toString("utf8"); +}; diff --git a/addons/cluster/temp/academy-handler.ts.txt b/addons/cluster/temp/academy-handler.ts.txt new file mode 100644 index 00000000..6c28243f --- /dev/null +++ b/addons/cluster/temp/academy-handler.ts.txt @@ -0,0 +1,25 @@ +import { processFileAnswers } from "@/reliverse/academy/fileProcessor"; +import consola from "consola"; + +import { errorMessage } from "~/utils"; + +// import { decryptAnswer, encryptAnswer, hashAnswer } from "@/reliverse/academy/crypto"; + +export const processAnswers = async ( + processFunction: (answer: string) => string, +): Promise<void> => { + try { + await processFileAnswers("questions", processFunction); + consola.success("Answers processed successfully."); + } catch (error) { + consola.error(`Failed to process answers: ${errorMessage(error)} `); + } +}; + +// No need to export these functions as they won't be used +// export const hashAll = (): Promise<void> => +// processAnswers((answer) => hashAnswer(decryptAnswer(answer))); + +// export const encryptAll = (): Promise<void> => processAnswers(encryptAnswer); + +// export const decryptAll = (): Promise<void> => processAnswers(decryptAnswer); diff --git a/addons/cluster/temp/academy-hash.ts.txt b/addons/cluster/temp/academy-hash.ts.txt new file mode 100644 index 00000000..691226da --- /dev/null +++ b/addons/cluster/temp/academy-hash.ts.txt @@ -0,0 +1,37 @@ +import { + decryptAnswer, + encryptAnswer, + hashAnswer, +} from "@/reliverse/academy/cryptoHandler"; +import { processFileAnswers } from "@/reliverse/academy/fileProcessor"; +import consola from "consola"; + +import { errorMessage } from "~/utils"; + +const processAnswers = async ( + processFunction: (answer: string) => string, +): Promise<void> => { + try { + await processFileAnswers("questions", processFunction); + consola.success("Answers processed successfully."); + } catch (error) { + consola.error(`Failed to process answers: ${errorMessage(error)} `); + } +}; + +export const encryptAll = (): Promise<void> => processAnswers(encryptAnswer); + +export const hashAll = (): Promise<void> => + processAnswers((answer) => hashAnswer(decryptAnswer(answer))); + +// Run encryption before hashing +const runEncryptionAndHashing = async (): Promise<void> => { + try { + await encryptAll(); + await hashAll(); + } catch (error) { + consola.error(`Failed to encrypt and hash answers: ${errorMessage(error)}`); + } +}; + +runEncryptionAndHashing().catch(consola.error); diff --git a/addons/cluster/temp/app.await.txt b/addons/cluster/temp/app.await.txt new file mode 100644 index 00000000..35f815c5 --- /dev/null +++ b/addons/cluster/temp/app.await.txt @@ -0,0 +1,249 @@ +import type { Icons } from "~/components/Common/Icons"; +import type { FooterItem, MainMenuItem } from "~/types"; + +import { config } from "@reliverse/core"; +import { loadConfig } from "c12"; +import consola from "consola"; +import { join } from "pathe"; + +import { productCategories } from "~/config/products"; +import { getCurrentDirname, slugify } from "~/utils"; + +const currentDirname = getCurrentDirname(import.meta.url); +const apptsJson = join(currentDirname, "json/appts"); + +// Load configuration using c12 +const loadSiteConfig = async () => { + const { config: configData } = await loadConfig({ + name: "appts", // Should match config file name without extension + cwd: apptsJson, + dotenv: true, + }); + + if (!configData) { + consola.error( + "Something went wrong! Configuration file `src/config/json/appts.json` was not found!", + ); + + return; + } + + // Default values for the site configuration + const defaultConfig = { + name: "Relivator", + appNameDesc: "Relivator: Next.js 15 and React 19 template by Reliverse", + appPublisher: "Reliverse", + appVersion: "1.2.6", + author: { + email: "blefnk@gmail.com", + fullName: "Nazar Kornienko", + handle: "blefnk", + handleAt: "@blefnk", + url: config.social.github, + }, + debug: false, + footerNav: [ + { + title: "Help", + items: [ + { title: "Contact", external: false, href: "/contact" }, + { title: "Privacy", external: false, href: "/privacy" }, + { title: "Terms", external: false, href: "/terms" }, + { title: "About", external: false, href: "/about" }, + ], + }, + { + title: "Social", + items: [ + { + title: "Github", + external: true, + href: "https://github.com/blefnk", + }, + { + title: "Discord", + external: true, + href: "https://discord.gg/Pb8uKbwpsJ", + }, + { title: "Twitter", external: true, href: "https://x.com/blefnk" }, + { + title: "Facebook", + external: true, + href: "https://facebook.com/groups/bleverse", + }, + ], + }, + { + title: "Github", + items: [ + { + title: "@reliverse", + external: true, + href: "https://github.com/orgs/reliverse/repositories", + }, + { + title: "@blefnk", + external: true, + href: "https://github.com/blefnk", + }, + { + title: "Relivator", + external: true, + href: "https://github.com/blefnk/relivator-nextjs-template", + }, + { + title: "Reliverse", + external: true, + href: "https://github.com/blefnk/reliverse-website-builder", + }, + ], + }, + { + title: "Support", + items: [ + { + title: "GitHub Sponsors", + external: true, + href: "https://github.com/sponsors/blefnk", + }, + { + title: "Buy Me a Coffee", + external: true, + href: "https://buymeacoffee.com/blefnk", + }, + { + title: "Patreon", + external: true, + href: "https://patreon.com/blefnk", + }, + { + title: "PayPal", + external: true, + href: "https://paypal.me/blefony", + }, + ], + }, + ] satisfies FooterItem[], + images: [ + { + alt: "Shows the cover image with the phrase: Relivator Empowers Your eCommerce with the Power of Next.js", + url: "/og-image.png", + }, + ], + keywords: ["next js shadcn ecommerce template"] as string[], + links: { + discord: "https://discord.gg/Pb8uKbwpsJ", + facebook: "https://facebook.com/groups/bleverse", + github: "https://github.com/blefnk/relivator-nextjs-template", + githubAccount: "https://github.com/blefnk", + twitter: "https://x.com/blefnk", + }, + mainNav: [ + { + title: "Catalogue", + href: "/", + items: [ + { + description: "All the products we have to offer", + title: "Products", + href: "/products", + items: [], + }, + { + description: "Build your own custom clothes", + title: "Build a Look", + href: "/custom/clothing", + items: [], + }, + { + description: "Read our latest blog posts", + title: "Blog", + href: "/blog", + items: [], + }, + ], + }, + ...productCategories.map((category) => ({ + title: category.title, + href: `/categories/${slugify(category.title)}`, + items: [ + { + description: `All ${category.title}.`, + title: "All", + href: `/categories/${slugify(category.title)}`, + items: [], + }, + ...category.subcategories.map((subcategory) => ({ + description: subcategory.description, + title: subcategory.title, + href: `/categories/${slugify(category.title)}/${subcategory.slug}`, + items: [], + })), + ], + })), + ] satisfies MainMenuItem[], + themeToggleEnabled: true, + }; + + // Merge the parsed configuration with the default configuration + return { + ...defaultConfig, + ...configData, + author: { + ...defaultConfig.author, + ...configData.author, + }, + }; +}; + +const socialLinks = { + discord: "https://discord.gg/Pb8uKbwpsJ", + facebook: "https://facebook.com/groups/bleverse", + github: "https://github.com/blefnk/relivator-nextjs-template", + githubAccount: "https://github.com/blefnk", + twitter: "https://x.com/blefnk", +}; + +// Load the site configuration +let siteConfig: Awaited<ReturnType<typeof loadSiteConfig>> | undefined; + +// Load the configuration asynchronously and export it +const initializeConfig = async () => { + siteConfig = await loadSiteConfig(); + + if (!siteConfig) { + consola.error("Failed to load site configuration"); + } +}; + +initializeConfig(); + +const getSiteConfig = async () => { + if (!siteConfig) { + await initializeConfig(); + } + + if (!siteConfig) { + throw new Error("Failed to load site configuration"); + } + + return siteConfig; +}; + +export { getSiteConfig, socialLinks }; + +// OAuth Providers Configuration +export const oauthProvidersClerk = [ + { name: "Google", icon: "view", strategy: "oauth_google" }, + { name: "Discord", icon: "discord", strategy: "oauth_discord" }, +] satisfies { + name: string; + icon: keyof typeof Icons; + strategy: + | "oauth_discord" + | "oauth_facebook" + | "oauth_github" + | "oauth_google" + | "oauth_microsoft"; +}[]; + diff --git a/addons/cluster/temp/app.ts.txt b/addons/cluster/temp/app.ts.txt new file mode 100644 index 00000000..caa8cb88 --- /dev/null +++ b/addons/cluster/temp/app.ts.txt @@ -0,0 +1,311 @@ +// [app.ts] Main App Configuration +// TODO: Restructure this file in Relivator 1.3.0. +// =============================== +import type { Icons } from "~/components/Common/Icons"; +import type { MainMenuItem } from "~/types"; + +import { productCategories } from "~/config/products"; +import { slugify } from "~/utils"; + +export const starterIsFree: boolean | undefined = true; + +type FooterItem = { + title: string; + items: { + title: string; + external?: boolean; + href: string; + }[]; +}; + +// TODO: parse this from clerk's dashboard +// TODO: instead of hardcoding it here +export const oauthProvidersClerk = [ + { + name: "Google", + icon: "view", + strategy: "oauth_google", + }, + { + name: "Discord", + icon: "discord", + strategy: "oauth_discord", + }, +] satisfies { + name: string; + icon: keyof typeof Icons; + strategy: + | "oauth_discord" + | "oauth_facebook" + | "oauth_github" + | "oauth_google" + | "oauth_microsoft"; +}[]; + +const appts = { + name: "Relivator", + debug: false, + version: "1.2.6", +}; + +export default appts; + +const links = { + discord: "https://discord.gg/Pb8uKbwpsJ", + facebook: "https://facebook.com/groups/bleverse", + github: "https://github.com/blefnk/relivator-nextjs-template", + githubAccount: "https://github.com/blefnk", + twitter: "https://x.com/blefnk", +}; + +export const contactConfig = { + email: "blefnk@gmail.com", +}; + +const REPOSITORY_OWNER = "blefnk"; + +export const settings = { + themeToggleEnabled: true, +}; + +export const siteConfig = { + description: // eslint-disable-next-line @stylistic/max-len + "Build More Efficient, Engaging, and Profitable Online Stores: Relivator Empowers your eCommerce with the Power of Next.js", + name: "Relivator", + author: "Nazar Kornienko", + company: { + name: "Relivator", + email: "blefnk@gmail.com", + link: "https://github.com/blefnk/relivator-nextjs-template", + twitter: "@blefnk", + }, + footerNav: [ + // { + // title: "Bleverse", + // items: config.internal.map((item) => ({ + // title: item.title, + // external: true, + // href: item.link, + // })), + // }, + { + title: "Help", + items: [ + { + title: "Contact", + external: false, + href: "/contact", + }, + { + title: "Privacy", + external: false, + href: "/privacy", + }, + { + title: "Terms", + external: false, + href: "/terms", + }, + { + title: "About", + external: false, + href: "/about", + }, + ], + }, + { + title: "Social", + items: [ + { + title: "Github", + external: true, + href: links.githubAccount, + }, + { + title: "Discord", + external: true, + href: links.discord, + }, + { + title: "Twitter", + external: true, + href: links.twitter, + }, + { + title: "Facebook", + external: true, + href: links.facebook, + }, + ], + }, + { + title: "Github", + items: [ + { + title: "@reliverse", + external: true, + href: "https://github.com/orgs/reliverse/repositories", + }, + { + title: "@blefnk", + external: true, + href: "https://github.com/blefnk", + }, + { + title: "Relivator", + external: true, + href: "https://github.com/blefnk/relivator-nextjs-template", + }, + { + title: "Reliverse", + external: true, + href: "https://github.com/blefnk/reliverse-website-builder", + }, + ], + }, + { + title: "Support", + items: [ + { + title: "GitHub Sponsors", + external: true, + href: "https://github.com/sponsors/blefnk", + }, + { + title: "Buy Me a Coffee", + external: true, + href: "https://buymeacoffee.com/blefnk", + }, + { + title: "Patreon", + external: true, + href: "https://patreon.com/blefnk", + }, + { + title: "PayPal", + external: true, + href: "https://paypal.me/blefony", + }, + ], + }, + ] satisfies FooterItem[], + handles: { + twitter: "@blefnk", + }, + keywords: [ + "App Router", + "Blefonix", + "Bleverse", + "Drizzle Orm", + "Landing Page", + "Next.js 15", + "Nextjs", + "Open Source", + "Parallel Routes", + "PostgreSQL", + "Radix Ui", + "React", + "Relivator", + "Server Actions", + "Server Components", + "shadcn/ui", + "Starter", + "Stripe", + "T3 Stack", + "Tailwind Css", + "Template", + "Tools", + "Utils", + ], + links, + mainNav: [ + { + title: "Catalogue", + href: "/", + items: [ + { + description: "All the products we have to offer.", + title: "Products", + href: "/products", + items: [], + }, + { + description: "Build the own custom clothes.", + title: "Build a Look", + href: "/custom/clothing", + items: [], + }, + { + description: "Read our latest blog posts.", + title: "Blog", + href: "/blog", + items: [], + }, + ], + }, + ...productCategories.map((category) => ({ + title: category.title, + href: `/categories/${slugify(category.title)}`, + items: [ + { + description: `All ${category.title}.`, + title: "All", + href: `/categories/${slugify(category.title)}`, + items: [], + }, + ...category.subcategories.map((subcategory) => ({ + description: subcategory.description, + title: subcategory.title, + href: `/categories/${slugify(category.title)}/${subcategory.slug}`, + items: [], + })), + ], + })), + ] satisfies MainMenuItem[], + shortName: "Relivator", + url: { + author: REPOSITORY_OWNER, + }, + version: "1.2.6", +}; + +export const companyName = siteConfig.company.name; + +export const siteName = siteConfig.name; + +// ============================================== +// DEPRECATED AND WILL BE REMOVED IN 1.3.0 +// ============================================== +// const emailAddress = config.social.email; +// export const emailWithoutMailto = emailAddress.replace(/^mailto:/i, ""); +// const REPOSITORY_NAME = "relivator"; +// const REPOSITORY_URL = config.framework.repo; +// export const DISCORD_URL = "https://discord.gg/Pb8uKbwpsJ"; +// export const BASE_URL = +// process.env.NODE_ENV === "production" ? getBaseUrl : "http://localhost:3000"; +// export const BRAND_NAME = "Relivator"; +// export const BRAND_DESCRIPTION = +// "Next.js 15 free store and dashboard template. +// It helps you build great eCommerce and SaaS apps faster than ever. Get it!"; +// export const OWNER_ROLE = "owner"; +// export const ADMIN_ROLE = "admin"; +// export const MEMBER_ROLE = "member"; +// export const TRIAL_LENGTH_IN_DAYS = 7; +// export const ROLES = [OWNER_ROLE, ADMIN_ROLE, MEMBER_ROLE] as const; +// const appts = { +// name: "Relivator", +// debug: false, +// version: "1.2.6", +// social: networks({ +// youtube: "@bleverse_com", +// discord: "Pb8uKbwpsJ", +// facebook: "groups/bleverse", +// twitter: "blefnk", +// github: "blefnk", +// }), +// }; +// export const DATABASE_URL = +// env.DATABASE_URL || +// process.env.DATABASE_URL || +// "username:password@hostname"; +// export const debugAfterLogs = process.env.NODE_ENV === "development"; diff --git a/addons/cluster/temp/auth-newest.txt b/addons/cluster/temp/auth-newest.txt new file mode 100644 index 00000000..c956d3d1 --- /dev/null +++ b/addons/cluster/temp/auth-newest.txt @@ -0,0 +1,276 @@ +// next-auth v5 | https://authjs.dev + + + +import { nanoid } from "nanoid"; +import NextAuth, { AuthError } from "next-auth"; +import Credentials from "next-auth/providers/credentials"; +import Discord from "next-auth/providers/discord"; +import GitHub from "next-auth/providers/github"; +import Google from "next-auth/providers/google"; + +import { db } from "~/db"; +import { users } from "~/db/schema"; + +import "next-auth/jwt"; + + + +// export type { +// Account, +// DefaultSession, +// Profile, +// Session, +// User, +// } from "@auth/core/types"; + +/** + * Options for NextAuth.js used to configure adapters, providers, callbacks, etc. + * + * @see https://authjs.dev/getting-started/installation#configure + */ +// const config = {/**/} satisfies NextAuthConfig; +export const { auth, handlers, signIn, signOut } = NextAuth({ + // adapter: DrizzleAdapter(db), + // env.DATABASE_URL.includes("postgres") ? pgTable : env.DATABASE_URL.includes("mysql") ? mysqlTable : sqliteTable, + // ) as Adapter, // TODO: node:stream error + basePath: "/api/auth", + callbacks: { + session: async ({ session, token }) => { + session.id = token.sub!; + + return session; + }, + signIn: async ({ account, user: userProvider }) => { + try { + if (account?.provider === "google") { + const { name, email, image } = userProvider; + + if (!email) { + throw new AuthError("Failed to sign in"); + } + + const isUserExist = (await db.select().from(users)).find((user) => user.email === email); + + if (!isUserExist) { + // create password and you mail it to user as temporary password + // so user can login with email and password too. + const password = nanoid(); + // const id = createId(); + + await db + .insert(users) + // @ts-expect-error TODO: Fix ts + .values({ + name: name as string, + email, + image: image as string, + password, + }) + .returning(); + } + + return true; + } else if (account?.provider === "credentials") { + return true; + } + + return false; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (error) { + throw new AuthError("Failed to sign in"); + } + }, + }, + pages: { + signIn: "/auth/sign-in", + }, + + // adapter: DrizzleAdapter(db, createTable) as Adapter, + providers: [ + Discord({ + allowDangerousEmailAccountLinking: true, + }), + GitHub({ + allowDangerousEmailAccountLinking: true, + }), + Google({ + allowDangerousEmailAccountLinking: true, + }), + Credentials({ + name: "Credentials", + credentials: { + id: { + /**/ + }, + name: { + /**/ + }, + email: { + /**/ + }, + }, + + // authorize(credentials, request) { + // }, + // authorize: async ({ email, id, name }) => { + // const data = { + // email: email as string, + // id: id as string, + // name: name as string, + // }; + // return data; + // }, + }), + ], + + theme: { + logo: "/logo.png", + }, + + // ============================================================ + // callbacks: { + // authorized({ request, auth }) { + // const { pathname } = request.nextUrl; + // if (pathname === "/middleware-example") return !!auth; + // return true; + // }, + // authorized: ({ request: { nextUrl }, auth: midAuth }) => { + // const isLoggedIn = Boolean(midAuth?.user); + // + // console.info("isLoggedIn:", isLoggedIn); + // + // const isOnDashboard = nextUrl.pathname.startsWith("/dashboard"); + // if (isOnDashboard) { + // Redirect unauthenticated users to the login page + // return isLoggedIn; + // } else if (isLoggedIn) { + // Redirect authenticated users to the dashboard + // return Response.redirect(new URL("/", nextUrl)); + // } + // Allow unauthenticated users to access other pages + // return true; + // }, + // jwt({ token, trigger, session, account }) { + // if (trigger === "update") token.name = session.name; + // if (account?.provider === "github") { + // return { ...token, accessToken: account.access_token }; + // } + // return token; + // }, + // session: ({ session, user }) => ({ + // }), + // session({ session, token, user }) { + // `session.address` is now a valid property, and will be type-checked + // in places like `useSession().data.user` or `auth().user` + // session.id = user.id; + // ============================================= + // ...session, + // user: { + // ...session, + // id: user.id, + // }, + // ============================================= + // if (token?.accessToken) { + // session.accessToken = token.accessToken; + // } + // return session; + // ============================================= + // ...session, + // user: { ...session, id: user.id }, + // return { + // ...session, + // user: { + // ...session, + // id: user.id, + // }, + // }; + // }, + // }, +}); + +/** + * Module augmentation for `next-auth` types. + * Allows us to add custom props to the + * `session` object and keep type safety. + * + * Returned by useSession, getSession and received + * as a prop on the SessionProvider React Context + * + * @see https://authjs.dev/getting-started/typescript#module-augmentation + */ +/* declare module "next-auth" { + interface Session extends DefaultSession { + user: { + id: string; + name: string; + email: string; + image?: string; + accessToken?: string; + // ...other props + // role: UserRole; + } & DefaultSession["user"]; + } + // interface User { + // } +} +*/ +// declare module "next-auth" { +// /** +// * The shape of the user object returned in the OAuth providers' `profile` callback, +// * or the second parameter of the `session` callback, when using a database. +// */ +// interface User { +// role: UserRole; +// } + +// /** +// * The shape of the account object returned in the OAuth providers' `account` callback, +// * Usually contains information about the provider being used, like OAuth tokens (`access_token`, etc). +// */ +// interface Account { +// /**/ +// } + +/** + * Returned by `useSession`, `auth`, contains information about the active session. + * Is received as a prop on the `SessionProvider` React Context. + */ +// interface Session extends DefaultSession { +// accessToken?: string; +// user: { +// /** The user's postal address. */ +// address: string; +// email: string; +// id: string; +// image?: string; + +// // ...other props +// // role: UserRole; +// name: string; + +// /** +// * By default, TypeScript merges new interface props and overwrites existing ones. +// * In this case, the default session user props will be overwritten, +// * with the new ones defined above. To keep the default session user props, +// * you need to add them back into the newly declared interface. +// */ +// } & DefaultSession["user"]; +// } +// } + +// declare module "next-auth/jwt" { +// // The main `JWT` interface can be found in the `next-auth/jwt` submodule +// /** Returned by the `jwt` callback and `auth`, when using JWT sessions */ +// interface JWT { +// accessToken?: string; + +// /** OpenID ID Token */ +// idToken?: string; +// } +// } + +// FOR DEBUG PURPOSES ONLY +// const debugConfig = { +// providers: [], +// } satisfies NextAuthConfig; diff --git a/addons/cluster/temp/auth-server.ts.txt b/addons/cluster/temp/auth-server.ts.txt new file mode 100644 index 00000000..727ec664 --- /dev/null +++ b/addons/cluster/temp/auth-server.ts.txt @@ -0,0 +1,228 @@ +/* eslint-disable complexity */ +import type { User } from "@clerk/nextjs/server"; +import type { Session } from "next-auth"; + +import { currentUser } from "@clerk/nextjs/server"; + +import { auth } from "~/auth"; +import { revalidateUser } from "~/core/server/populate-user-auth"; +import { env } from "~/env"; + +type SellerProps = { + id: string; + name?: string; + slug?: string; + domains?: { + slug: string; + }[]; + logo?: string; + users?: { + role: "admin" | "buyer" | "seller"; + }[]; + createdAt?: Date; +}; + +// Temporary wrapper for `revalidateUser` +// `revalidateUser` function handles user authentication and session validation. +// Tasks: verify user sessions, revalidate user data, delegate authentication. +// logic to provider-specific functions based on the configured authentication provider. +// It ensures user data consistency across the application by handling cases where +// data might be absent or outdated due to database updates. +export async function getDeprecatedServerAuthSession() { + // TODO: FIX + // const user = await revalidateUser(); + // return user; + const authProvider = env.NEXT_PUBLIC_AUTH_PROVIDER || "authjs"; + + if (authProvider === "authjs") { + const session = await auth(); + + return session; + } + + if (authProvider === "clerk") { + // const session = await clerkClient.users.getUser(userId); + // return session; + return currentUser(); + } + + throw new Error( + "❌ [getDeprecatedServerAuthSession()] Allowed values for 'NEXT_PUBLIC_AUTH_PROVIDER' are 'authjs' and 'clerk'", + ); +} + +// eslint-disable-next-line sonar/function-name +export async function сurrentUser() { + // const session = await auth(); + // return session?.user; + return await revalidateUser(); +} + +// export const getUserById = async (userId: string) => { +// const user = await db +// .select() +// .from(users) +// .where(eq(users.id, userId)) +// .then((res) => res[0] || null); +// return user; +// }; +// export function getUserEmail(user: User | null) { +// const email = +// user?.emailAddresses?.find((e) => e.id === user.primaryEmailAddressId) +// ?.emailAddress || ""; +// return email; +// } +type UserDataFields = { + email?: string; + image?: string; + initials?: string; + username?: string; +}; + +type UserDataOptions = { + [K in keyof UserDataFields]?: boolean; +}; + +export async function getUserData( + user: User | null, + dataTypes: UserDataOptions = { + email: true, + image: true, + initials: true, + username: true, + }, +): Promise<UserDataFields> { + const res: UserDataFields = { + // + }; + + const authProvider = env.NEXT_PUBLIC_AUTH_PROVIDER || "authjs"; + + if (authProvider === "clerk") { + if (dataTypes.email) { + res.email = + (user && + user.emailAddresses && + user.emailAddresses.find( + (event_: { + id: unknown; + }) => event_.id === user.primaryEmailAddressId, + ) && // @ts-expect-error TODO: fix + user.emailAddresses.find( + (event_: { + id: unknown; + }) => event_.id === user.primaryEmailAddressId, + ).emailAddress) || + ""; + } + + if (dataTypes.initials) { + // eslint-disable-next-line @stylistic/max-len + res.initials = `${(user && user.firstName && user.firstName.charAt(0)) || ""} ${(user && user.lastName && user.lastName.charAt(0)) || ""}`; + } + + if (dataTypes.username) { + res.username = `${(user && user.firstName) || ""} ${(user && user.lastName) || ""}`; + } + + if (dataTypes.image) { + res.image = (user && user.imageUrl) || ""; + } + } else if (authProvider === "authjs") { + const session = await auth(); + + if (dataTypes.email) { + res.email = (session.email) || ""; + } + + if (dataTypes.username) { + res.username = String( + (session.name) || "", + ); + } + + if (dataTypes.initials) { + res.initials = String( + (session && + session && + session.name && + session.name.charAt(0)) || + "", + ); + } + + if (dataTypes.image) { + res.image = (session.image) || ""; + } + } else { + throw new Error("❌ [users.ts] Invalid auth provider."); + } + + return res; +} + +function getSearchParameters(url: string) { + const parameters: Record<string, string> = { + // + }; + + for (const [key, value] of new URL(url).searchParams.entries()) { + parameters[key] = value; + } + + return parameters; +} + +type WithAuthHandler = ({ + domain, + headers, + params, + req, + searchParams, + seller, + session, +}: { + domain?: string; + headers?: Record<string, string>; + params?: Record<string, string>; + req?: Request; + searchParams?: Record<string, string>; + seller?: SellerProps; + session?: Session | undefined; +}) => Promise<Response>; + +// This file is used to wrap API routes with auth. +// TODO: Not finished yet and not used anywhere. +// @see https://github.com/steven-tey/dub/blob/main/apps/web/lib/auth/index.ts +export const withAuth = + (handler: WithAuthHandler) => + ( + request: Request, + { + params, + }: { + params: Record<string, string> | undefined; + }, + ) => { + const searchParameters = getSearchParameters(request.url); + + // const { domain, linkId, slug } = + // params || + // { + // + // }; + let session: Session | undefined; + const seller: SellerProps | undefined = undefined; + + return handler({ + params: + params || + { + // + }, + req: request, + searchParams: searchParameters, + seller, + session, + }); + }; diff --git a/addons/cluster/temp/auth.ts.txt b/addons/cluster/temp/auth.ts.txt new file mode 100644 index 00000000..10ce2f36 --- /dev/null +++ b/addons/cluster/temp/auth.ts.txt @@ -0,0 +1,101 @@ +import type { User } from "~/db/schema"; + +import { currentUser as ClerkAuth, clerkClient } from "@clerk/nextjs/server"; +import consola from "consola"; +import NextAuth from "next-auth"; +import GitHub from "next-auth/providers/github"; + +import { env } from "~/env"; + +const fakeSession = async (): Promise<User> => ({ + id: "fakeId", + name: "fakeName", + currentCartId: "fakeCurrentCartId", + email: "fake@example.com", + emailVerified: new Date("2024-07-10T00:00:00.000Z"), + hashedPassword: "fakeHashedPassword", + image: "https://relivator.bleverse.com/logo.png", + mode: "seller", + role: "user", + stripeCurrentPeriodEnd: "fakeStripeCurrentPeriodEnd", + stripeCustomerId: "fakeStripeCustomerId", + stripePriceId: "fakeStripePriceId", + stripeSubscriptionId: "fakeStripeSubscriptionId", + createdAt: "fakeCreatedAt", + updatedAt: "fakeUpdatedAt", +}); + +const clerkSession = async (): Promise<User> => { + try { + const user = await ClerkAuth(); + const fake = await fakeSession(); + + if (!user) { + return fake; + } + + const emailResponse = await clerkClient.emailAddresses.getEmailAddress( + user.primaryEmailAddressId || "", + ); + + return { + ...fake, + id: user.id, + name: `${user.firstName} ${user.lastName}`.trim(), + email: emailResponse.emailAddress, + image: user.imageUrl, + }; + } catch (error) { + consola.error("Error fetching Clerk user:", error); + + return await fakeSession(); + } +}; + +const authjsSession = async (): Promise<User> => { + try { + const { auth } = NextAuth({ providers: [GitHub] }); + + const user = await auth(); + const fake = await fakeSession(); + + if (!user || !user.user) { + return fake; + } + + return { + ...fake, + id: user.user.id || "", + name: user.user.name || "", + email: user.user.email || "", + image: user.user.image || "", + }; + } catch (error) { + consola.error("Error fetching Auth.js user:", error); + + return await fakeSession(); + } +}; + +export async function auth(): Promise<User> { + if (!env.DATABASE_URL) { + return await fakeSession(); + } + + if (!env.NEXT_PUBLIC_AUTH_PROVIDER) { + consola.warn( + "Please set or correct NEXT_PUBLIC_AUTH_PROVIDER in your .env file to enable user authentication with real data. The app is currently using fake data.", + ); + + return await fakeSession(); + } + + switch (env.NEXT_PUBLIC_AUTH_PROVIDER) { + case "authjs": + return await authjsSession(); + case "clerk": + return await clerkSession(); + default: + return await fakeSession(); + } +} diff --git a/addons/cluster/temp/auth/adapter.ts.txt b/addons/cluster/temp/auth/adapter.ts.txt new file mode 100644 index 00000000..188689ac --- /dev/null +++ b/addons/cluster/temp/auth/adapter.ts.txt @@ -0,0 +1,94 @@ +import { + Adapter, + AdapterAccount, + AdapterAuthenticator, +} from "next-auth/adapters"; + +import { DrizzleAdapter } from "@auth/drizzle-adapter"; +import { and, eq } from "drizzle-orm"; +import { randomUUID } from "uncrypto"; + +import { db } from "~/db"; +import { + accounts, + authenticators, + sessions, + users, + verificationTokens, +} from "~/db/schema"; + +// Drizzle Adapter with Passkey +export const drizzleAdapter = { + ...DrizzleAdapter(db, { + accountsTable: accounts, + sessionsTable: sessions, + usersTable: users, + verificationTokensTable: verificationTokens, + }), + createAuthenticator: async (data) => { + const id = randomUUID(); + + await db.insert(authenticators).values({ + id, + ...data, + }); + const [authenticator] = await db + .select() + .from(authenticators) + .where(eq(authenticators.id, id)); + + // @ts-expect-error TODO: Fix + const { id: _, transports, ...rest } = authenticator; + + return { + ...rest, + transports: transports || undefined, + }; + }, + getAccount: async (providerAccountId, provider) => { + const [account] = await db + .select() + .from(accounts) + .where( + and( + eq(accounts.provider, provider), + eq(accounts.providerAccountId, providerAccountId), + ), + ); + + return (account as AdapterAccount) || null; + }, + getAuthenticator: async (credentialId) => { + const [authenticator] = await db + .select() + .from(authenticators) + .where(eq(authenticators.credentialID, credentialId)); + + return (authenticator as AdapterAuthenticator) || null; + }, + listAuthenticatorsByUserId: async (userId) => { + const authenticator = await db + .select() + .from(authenticators) + .where(eq(authenticators.userId, userId)); + + return authenticator.map((a: unknown) => ({ + ...a, + transports: a.transports || undefined, + })); + }, + updateAuthenticatorCounter: async (credentialId, counter) => { + await db + .update(authenticators) + .set({ + counter, + }) + .where(eq(authenticators.credentialID, credentialId)); + const [authenticator] = await db + .select() + .from(authenticators) + .where(eq(authenticators.credentialID, credentialId)); + + return (authenticator as AdapterAuthenticator) || null; + }, +} satisfies Adapter; diff --git a/addons/cluster/temp/auth/config.ts.txt b/addons/cluster/temp/auth/config.ts.txt new file mode 100644 index 00000000..6cf72f36 --- /dev/null +++ b/addons/cluster/temp/auth/config.ts.txt @@ -0,0 +1,55 @@ +import { DefaultSession, NextAuthConfig } from "next-auth"; + +declare module "next-auth" { + // eslint-disable-next-line @typescript-eslint/consistent-type-definitions, @typescript-eslint/naming-convention + interface Session { + user: { + id: string; + name: string; + email: string; + image?: string; + } & DefaultSession["user"]; + } +} + +export type Session = { + user: { + id: string; + name: string; + email: string; + image?: string; + } & DefaultSession["user"]; +}; + +export const authConfig = { + callbacks: { + // @ts-expect-error TODO: fix + authorized({ auth, request: { nextUrl } }) { + const isLoggedIn = !!auth && auth.user; + const isOnBoards = nextUrl.pathname.startsWith("/boards"); + + if (isOnBoards) { + return isLoggedIn; + } + + return true; + }, + session: async ({ session, token }) => { + if (token && token.sub) { + session.id = token.sub; + } + + return session; + }, + }, + experimental: { + enableWebAuthn: true, + }, + pages: { + signIn: "/", + }, + providers: [], + session: { + strategy: "jwt", + }, +} satisfies NextAuthConfig; diff --git a/addons/cluster/temp/auth/config2.ts.txt b/addons/cluster/temp/auth/config2.ts.txt new file mode 100644 index 00000000..39d156cc --- /dev/null +++ b/addons/cluster/temp/auth/config2.ts.txt @@ -0,0 +1,39 @@ +import { DefaultSession } from "next-auth"; + +import NextAuth from "next-auth"; +import github from "next-auth/providers/github"; + +import { env } from "~/env"; + +declare module "next-auth" { + // eslint-disable-next-line @typescript-eslint/consistent-type-definitions + interface Session { + user: { + id: string; + name: string; + email: string; + image?: string; + } & DefaultSession["user"]; + } +} + +export type Session = { + user: { + id: string; + name: string; + email: string; + image?: string; + } & DefaultSession["user"]; +}; + +export const { auth, handlers, signIn, signOut, unstable_update } = NextAuth(() => { + return { + basePath: "/api/auth", + debug: true, + experimental: { + enableWebAuthn: true, + }, + providers: [github], + secret: env.AUTH_SECRET, + }; +}); diff --git a/addons/cluster/temp/auth/index2.ts.txt b/addons/cluster/temp/auth/index2.ts.txt new file mode 100644 index 00000000..684cec0e --- /dev/null +++ b/addons/cluster/temp/auth/index2.ts.txt @@ -0,0 +1,172 @@ +"use server"; + +import { cache } from "react"; + +import { randomBytes, scrypt, timingSafeEqual } from "crypto"; + +import { redirect } from "next/navigation"; +import NextAuth from "next-auth"; +import Credentials from "next-auth/providers/credentials"; +import Github from "next-auth/providers/github"; +import Passkey from "next-auth/providers/passkey"; +import { z } from "zod"; + +import { drizzleAdapter } from "~/auth/adapter"; +import { authConfig } from "~/auth/config"; +import { db } from "~/db"; +import { users } from "~/db/schema"; + +const log = console; + +// createLogger('auth') +async function hash(password: string) { + return new Promise<string>((resolve, reject) => { + const salt = randomBytes(16).toString("hex"); + + scrypt(password, salt, 64, (error, derivedKey) => { + if (error) { + log.error("Error hashing password", error); + reject(error); + } + + resolve(`${salt}.${derivedKey.toString("hex")}`); + }); + }); +} + +async function compare(password: string, hash: string) { + return new Promise<boolean>((resolve, reject) => { + const [salt, hashKey] = hash.split("."); + + // @ts-expect-error TODO: Fix + scrypt(password, salt, 64, (error, derivedKey) => { + if (error) { + log.error("Error comparing password", error); + reject(error); + } + + // @ts-expect-error TODO: Fix + resolve(timingSafeEqual(Buffer.from(hashKey, "hex"), derivedKey)); + }); + }); +} + +const { + auth: uncachedAuth, + handlers: { GET, POST }, + signIn, + signOut, +} = NextAuth({ + ...authConfig, + adapter: drizzleAdapter, + experimental: { + enableWebAuthn: true, + }, + logger: { + debug: (message, metadata) => + log.debug(`${message} %o`, { + metadata, + }), + error: (error) => log.error(error), + warn: (message) => { + if (message.includes("experimental-webauthn")) { + // don't spam the console with this + return; + } + + log.warn(message); + }, + }, + providers: [ + Github, + Passkey, + Credentials({ + name: "Credentials", + async authorize(c) { + const credentials = z + .object({ + password: z.string().min(6).max(32), + username: z.string().min(1).max(32), + }) + .safeParse(c); + + if (!credentials.success) { + return null; + } + + try { + const user = await db.query.users.findFirst({ + where: (fields, ops) => + ops.sql`${fields.name} = ${credentials.data.username} COLLATE NOCASE`, + }); + + if (user) { + if (!user.hashedPassword) { + log.debug(`OAuth User ${user.id} attempted signin with password`); + + return null; + } + + const pwMatch = await compare( + credentials.data.password, + user.hashedPassword, + ); + + if (!pwMatch) { + log.debug(`User ${user.id} attempted login with bad password`); + + return null; + } + + return { + id: user.id, + name: user.name, + }; + } + + // Auto-signup new users - whatever... + log.debug(`Auto-signup new user ${credentials.data.username}`); + const result = await db + .insert(users) + .values({ + name: credentials.data.username, + email: `${credentials.data.username}@example.com`, + hashedPassword: await hash(credentials.data.password), + }) + .returning(); + + if (result.length === 0 || !result[0]) { + throw new Error("User signup failed"); + } + + const newUser = result[0]; + + return { + id: newUser.id, + name: newUser.name, + }; + } catch (error) { + log.error("Error during user signup", error); + + return null; + } + }, + }), + ], +}); + +export { GET, POST, signIn, signOut }; + +export const auth = cache(uncachedAuth); + +export const currentUser = cache(async () => { + const session = await auth(); + + // @ts-expect-error TODO: fix + if (!session) { + redirect("/"); + } + + // @ts-expect-error TODO: fix + return session; +}); diff --git a/addons/cluster/temp/ava.config.js.txt b/addons/cluster/temp/ava.config.js.txt new file mode 100644 index 00000000..55903889 --- /dev/null +++ b/addons/cluster/temp/ava.config.js.txt @@ -0,0 +1,31 @@ +// 🔴 DEPRECATED AND POSSIBLY WILL BE REMOVED IN RELIVATOR 1.3.0 🔴 || +// Starting Relivator 1.3.0, it can be added by using pnpm reliverse || +// ================================================================= || + +/** + * Ava Testing Library Configuration + * ================================= + * + * todo: compile ts files before ava running + * + * @see https://github.com/avajs/ava#why-ava + * @see https://typestrong.org/ts-node/docs + */ + +import { register } from "module"; +import { fileURLToPath, pathToFileURL } from "url"; + +const filename = fileURLToPath(import.meta.url); + +register("ts-node/esm", pathToFileURL(filename)); + +const avaConfig = { + files: ["src/tests/ava/**/*.ts"], + typescript: { + compile: false, + extensions: ["ts", "tsx"], + rewritePaths: { "src/": "src/tests/ava/swc/" }, + }, +}; + +export default avaConfig; diff --git a/addons/cluster/temp/babel.config.txt b/addons/cluster/temp/babel.config.txt new file mode 100644 index 00000000..ee58bc24 --- /dev/null +++ b/addons/cluster/temp/babel.config.txt @@ -0,0 +1,17 @@ +// 🔴 DEPRECATED AND POSSIBLY WILL BE REMOVED IN RELIVATOR 1.3.0 🔴 || +// @see https://react.dev/learn/react-compiler#usage-with-babel +// ================================================================= || + +const ReactCompilerConfig = { + /* ... */ +}; + +// eslint-disable-next-line unicorn/no-anonymous-default-export +module.exports = function () { + return { + plugins: [ + ["babel-plugin-react-compiler", ReactCompilerConfig], // must run first! + // ... + ], + }; +}; diff --git a/addons/cluster/temp/biome-new.json.txt b/addons/cluster/temp/biome-new.json.txt new file mode 100644 index 00000000..33b01688 --- /dev/null +++ b/addons/cluster/temp/biome-new.json.txt @@ -0,0 +1,184 @@ +{ + "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", + "files": { + "include": [ + "./**/*.js", + "./**/*.ts", + "./**/*.jsx", + "./**/*.tsx", + "./**/*.css", + "./**/*.json" + ] + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "style": { + "noNonNullAssertion": "off", + "noUselessElse": "off", + "useEnumInitializers": "off", + "useExportType": "off", + "useImportType": "off", + "useNamingConvention": "off", + "useShorthandFunctionType": "off", + "useWhile": "off", + "useNumberNamespace": "off", + "useNodejsImportProtocol": "off" + }, + "complexity": { + "useLiteralKeys": "off", + "noForEach": "off", + "useArrowFunction": "off", + "useFlatMap": "off", + "useOptionalChain": "off", + "useRegexLiterals": "off", + "useSimpleNumberKeys": "off", + "noUselessLoneBlockStatements": "off", + "noBannedTypes": "off", + "noUselessFragments": "off" + }, + "a11y": { + "noSvgWithoutTitle": "off", + "noBlankTarget": "off", + "useKeyWithClickEvents": "off", + "noNoninteractiveElementToInteractiveRole": "off" + }, + "performance": { + "noAccumulatingSpread": "off" + }, + "suspicious": { + "noImplicitAnyLet": "off", + "noArrayIndexKey": "off", + "noAssignInExpressions": "off", + "noApproximativeNumericConstant": "off", + "noCommentText": "off", + "noCompareNegZero": "off", + "noConstEnum": "off", + "noDoubleEquals": "off", + "noDuplicateObjectKeys": "off", + "noEmptyInterface": "off", + "noExplicitAny": "off", + "noExtraNonNullAssertion": "off", + "noGlobalIsFinite": "off", + "noGlobalIsNan": "off", + "noMisleadingCharacterClass": "off", + "noMisrefactoredShorthandAssign": "off", + "noSelfCompare": "off", + "noSparseArray": "off", + "noUnsafeNegation": "off", + "useIsArray": "off", + "useNamespaceKeyword": "off", + "useValidTypeof": "off", + "noConfusingLabels": "off" + }, + "correctness": { + "noChildrenProp": "off", + "noEmptyPattern": "off", + "noInvalidConstructorSuper": "off", + "noInvalidNewBuiltin": "off", + "noInvalidUseBeforeDeclaration": "off", + "noPrecisionLoss": "off", + "noRenderReturnValue": "off", + "noStringCaseMismatch": "off", + "noSwitchDeclarations": "off", + "noUndeclaredVariables": "off", + "noUnnecessaryContinue": "off", + "noUnreachable": "off", + "noUnreachableSuper": "off", + "noUnsafeFinally": "off", + "noUnsafeOptionalChaining": "off", + "noUnusedLabels": "off", + "noUnusedPrivateClassMembers": "off", + "noUnusedVariables": "off", + "noVoidElementsWithChildren": "off", + "noVoidTypeReturn": "off", + "useExhaustiveDependencies": "off", + "useHookAtTopLevel": "off", + "useIsNan": "off", + "useValidForDirection": "off", + "useYield": "off" + } + }, + "ignore": [ + ".million/", + ".next/", + ".turbo/", + "node_modules/", + "drizzle/", + "src/styles/globals.css" + ] + }, + "organizeImports": { + "enabled": false + }, + "formatter": { + "enabled": true, + "formatWithErrors": false, + "attributePosition": "auto", + "indentStyle": "space", + "indentWidth": 2, + "lineEnding": "lf", + "lineWidth": 80, + "ignore": [ + ".million/", + ".next/", + ".turbo/", + "node_modules/", + "drizzle/", + "src/styles/globals.css" + ] + }, + "javascript": { + "formatter": { + "arrowParentheses": "always", + "bracketSameLine": false, + "bracketSpacing": true, + "jsxQuoteStyle": "double", + "quoteProps": "asNeeded", + "lineWidth": 80, + "semicolons": "always", + "trailingCommas": "all", + "attributePosition": "auto", + "indentStyle": "space", + "lineEnding": "lf", + "quoteStyle": "single", + "enabled": false, + "indentWidth": 2 + } + }, + "css": { + "formatter": { + "enabled": true, + "indentStyle": "space", + "quoteStyle": "double", + "indentWidth": 2, + "lineEnding": "lf", + "lineWidth": 80 + }, + "linter": { + "enabled": true + }, + "parser": { + "allowWrongLineComments": false, + "cssModules": true + } + }, + "json": { + "formatter": { + "enabled": true, + "trailingCommas": "none", + "indentWidth": 2, + "lineWidth": 80, + "indentStyle": "space", + "lineEnding": "lf" + }, + "parser": { + "allowComments": false, + "allowTrailingCommas": false + }, + "linter": { + "enabled": true + } + } +} diff --git a/addons/cluster/temp/biome.json.txt b/addons/cluster/temp/biome.json.txt new file mode 100644 index 00000000..32ef5774 --- /dev/null +++ b/addons/cluster/temp/biome.json.txt @@ -0,0 +1,31 @@ +// 🔴 DEPRECATED AND POSSIBLY WILL BE REMOVED IN RELIVATOR 1.3.0 🔴 || +// ================================================================= || + +{ + "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", + "organizeImports": { + "enabled": false + }, + "files": { + "ignore": ["node_modules"] + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "style": { + "noUselessElse": "off" + }, + "suspicious": { + "noExplicitAny": "off", + "noArrayIndexKey": "off" + }, + "correctness": { + "useExhaustiveDependencies": "off" + }, + "complexity": { + "useLiteralKeys": "off" + } + } + } +} diff --git a/addons/cluster/temp/commitlint.config.cjs.txt b/addons/cluster/temp/commitlint.config.cjs.txt new file mode 100644 index 00000000..9cd2a3f2 --- /dev/null +++ b/addons/cluster/temp/commitlint.config.cjs.txt @@ -0,0 +1,24 @@ +// 🔴 DEPRECATED AND POSSIBLY WILL BE REMOVED IN RELIVATOR 1.3.0 🔴 || +// Starting Relivator 1.3.0, it can be added by using pnpm reliverse || +// ================================================================= || + +/** + * @see https://github.com/logto-io/logto/blob/master/commitlint.config.cjs + * @see https://github.com/search?q=path%3Acommitlint.config.cjs&type=code + */ + +// const { rules } = require("@commitlint/config-conventional"); +// const isCi = process.env.CI === "true"; + +/** @type {import('@commitlint/types').UserConfig} **/ +module.exports = { + extends: ["@commitlint/config-conventional"], + rules: { + // "type-enum": [2, "always", [...rules["type-enum"][2], "api", "release"]], + // 'scope-enum': [2, 'always', ['connector', 'console', 'core', 'demo-app', 'test', + // 'phrases', 'schemas', 'shared', 'experience', 'deps', 'deps-dev', 'cli', 'toolkit', 'cloud', 'app-insights']], + // Slightly increase the tolerance to allow the appending PR number + // ...(isCi && { "header-max-length": [2, "always", 110] }), + // "body-max-line-length": [2, "always", 110], + }, +}; diff --git a/addons/cluster/temp/config/json/appts.json.txt b/addons/cluster/temp/config/json/appts.json.txt new file mode 100644 index 00000000..4ba7ea9f --- /dev/null +++ b/addons/cluster/temp/config/json/appts.json.txt @@ -0,0 +1,13 @@ +{ + "appNameDesc": "Relivator: Next.js 15 and React 19 template by Reliverse", + "appPublisher": "Reliverse", + "appVersion": "1.2.6", + "author": { + "email": "blef@gmail.com", + "fullName": "Nazar Kornienko", + "handle": "blefnk", + "handleAt": "@blefnk", + "url": "https://github.com/blefnk" + }, + "name": "Relivator" +} diff --git a/addons/cluster/temp/cors.js.txt b/addons/cluster/temp/cors.js.txt new file mode 100644 index 00000000..3ef4471f --- /dev/null +++ b/addons/cluster/temp/cors.js.txt @@ -0,0 +1,80 @@ +/* eslint-disable no-relative-import-paths/no-relative-import-paths */ +// import { createSecureHeaders } from "next-secure-headers"; + +import corsPolicyString from "./csp.js"; +import { env } from "./env.js"; + +export async function getHeaders() { + // Please note: it is still experimental, so + // NEXT_PUBLIC_CSP_XSS is "false" by default + + if (env.NEXT_PUBLIC_CSP_XSS === "true") { + const headers = []; + + // Prevent search engines from indexing the site if it is not live + // This is useful for staging environments before they are ready to go live + if (env.NEXT_PUBLIC_IS_LIVE === "false") { + // To allow robots to crawl the site, use the NEXT_PUBLIC_IS_LIVE env variable + // You may want to also use this variable to conditionally render any tracking scripts + headers.push({ + // @see https://github.com/payloadcms/payload/blob/main/templates/ecommerce/next.config.js + headers: [ + { + key: "X-Robots-Tag", + value: "noindex", + }, + ], + source: "/:path*", + }); + } + + // Set the Content-Security-Policy header as a security measure to prevent XSS attacks + // It works by explicitly whitelisting trusted sources of content for the website + headers.push({ + // todo: make it more stable | currently too much things are allowed than needed + headers: [ + { + // todo: looks like we need to specify some policies + // todo: here & some in images.contentSecurityPolicy + key: "Content-Security-Policy", + value: corsPolicyString, + }, + { + key: "Access-Control-Allow-Credentials", + value: "true", + }, + { + key: "Access-Control-Allow-Origin", + value: "*", + }, + { + key: "Access-Control-Allow-Methods", + value: "GET,DELETE,PATCH,POST,PUT", + }, + { + key: "Access-Control-Allow-Headers", + value: + // eslint-disable-next-line @stylistic/max-len + "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version", + }, + ], + + // todo: @see src/core/cors/csp.js for more details | work in progress | not fully tested + // This will block all inline scripts and styles except for those that are allowed + source: "/(.*)", + }); + + // Note: to apply CSP changes while dev runtime, + //Cmd/Ctrl+S this file, to reload Next.js' server. + return headers; + } else { + return [ + // default option: using next-secure-headers csp library + // @see https://github.com/jagaapple/next-secure-headers + { + // headers: createSecureHeaders(), + source: "/(.*)", + }, + ]; + } +} diff --git a/addons/cluster/temp/csp.js.txt b/addons/cluster/temp/csp.js.txt new file mode 100644 index 00000000..8db8afb2 --- /dev/null +++ b/addons/cluster/temp/csp.js.txt @@ -0,0 +1,243 @@ +/* eslint-disable no-relative-import-paths/no-relative-import-paths */ +/** + * Content Security Policy + * ======================= + * + * TODO: THIS FILE IS STILL A WORK IN PROGRESS + * TODO: TO ENABLE: NEXT_PUBLIC_CSP_XSS="TRUE" + * + * This file configures the Content Security Policy (CSP) for the application. + * It's designed to be modular and easily adjustable to fit different environments. + * Regularly review and update the CSP policy to align with security best practices. + * + * This file can be de/activated by toggling boolean NEXT_PUBLIC_CSP_XSS value in .env. + * For CSP changes in dev runtime, save next.config.js file to reload Next.js' server. + * + * todo: looks like we need to specify some policies here and some + * todo: in images.contentSecurityPolicy (of next.config.js file) + * + * @see https://github.com/payloadcms/payload/blob/main/templates/ecommerce/csp.js + */ + +import { env } from "./env.js"; + +// import { fileURLToPath } from "url"; +// import createJITI from "jiti"; +// const env = createJITI(fileURLToPath(import.meta.url))("./src/env"); + +// CSP Utils (scroll down for CSP Directives) +// ------------------------------------------ + +/** + * Determines whether the environment is development. + * @returns {boolean} Whether the environment is dev. + */ +const isDevelopmentEnv = () => { + return env.NODE_ENV === "development"; +}; + +/** + * Determines the protocol based on the environment. + * @returns {string} The protocol (http or https). + */ +const determineProtocol = () => { + return isDevelopmentEnv() ? "http" : "https"; +}; + +/** + * Extracts the domain from a URL. + * @param {string} url - The URL to extract the domain from. + * @returns {string} The extracted domain or an empty string if the URL is invalid. + */ +const extractDomain = (url) => { + try { + return new URL(url).hostname; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (error) { + consola.error("Invalid URL in CSP policy:", url); + + return ""; + } +}; + +/** + * Determines the application's domain for use in CSP directives. + * Read the details in the end of this file for more information. + */ +const appDomain = () => { + if (env.NEXT_PUBLIC_APP_URL) { + return extractDomain(env.NEXT_PUBLIC_APP_URL); + } + + if (isDevelopmentEnv() || !env.VERCEL_URL) { + return "localhost:3000"; + } + + return extractDomain(env.VERCEL_URL); +}; + +// [ContentSecurityPolicy] CSP Directives +// -------------------------------------- + +// Base directives common to multiple policies. +const baseDirectives = () => ({ + "default-src": ["'self'"], + "font-src": [ + "'self'", + `${determineProtocol()}://*.${appDomain()}`, + env.ADDITIONAL_CSP_ORIGINS, + env.NEXT_PUBLIC_APP_URL, + ], + "style-src": [ + "'self'", + "'unsafe-hashes'", + "'unsafe-inline'", + "'nonce-tanstack'", + "https://*.googleapis.com", + + // "https://fonts.googleapis.com", + ], +}); + +/** + * Specific directives for different resource types. + * Adjust these directives as per the application's requirements. + */ +const scriptSourceDirectives = () => { + // Example for development environment usage only + // if (isDevEnv()) { directives.push("'unsafe-inline'", "'unsafe-eval'"); } + return [ + ...baseDirectives()["default-src"], + "'unsafe-eval'", + "'unsafe-inline'", + "https://*.clerk.accounts.dev", + "https://*.google.com", + "https://*.googleapis.com", + "https://*.stripe.com", + "https://challenges.cloudflare.com", + "https://va.vercel-scripts.com", + + // "https://accounts.google.com", + // "https://checkout.stripe.com", + // "https://js.stripe.com", + // "https://maps.googleapis.com", + `${determineProtocol()}://*.${appDomain()}`, + env.ADDITIONAL_CSP_ORIGINS, + env.NEXT_PUBLIC_APP_URL, + ]; +}; + +/** + * Specific directives for different resource types. + * Adjust these directives as per the application's requirements. + */ +const specificDirectives = () => ({ + "child-src": ["'self'", "blob:"], + "connect-src": [ + "'self'", + "https://*.clerk.accounts.dev", + "https://*.google.com", + "https://*.googleapis.com", + "https://*.stripe.com", + + // "https://accounts.google.com", + // "https://api.stripe.com", + // "https://checkout.stripe.com", + // "https://maps.googleapis.com", + `${determineProtocol()}://*.${appDomain()}`, + env.ADDITIONAL_CSP_ORIGINS, + env.NEXT_PUBLIC_APP_URL, + ], + "frame-src": [ + "'self'", + "https://*.google.com", + "https://*.stripe.com", + "https://challenges.cloudflare.com", + + // "https://accounts.google.com", + // "https://checkout.stripe.com", + // "https://hooks.stripe.com", + // "https://js.stripe.com", + `${determineProtocol()}://*.${appDomain()}`, + env.ADDITIONAL_CSP_ORIGINS, + env.NEXT_PUBLIC_APP_URL, + ], + "img-src": [ + "'self'", + "data:", + "https://*.clerk.com", + "https://*.githubusercontent.com", + "https://*.googleusercontent.com", + "https://*.stripe.com", + "https://*.youtube.com", + "https://api.dicebear.com", + "https://authjs.dev", + "https://cdn.discordapp.com", + "https://discordapp.com", + "https://githubusercontent.com", + "https://googleusercontent.com", + "https://i.imgur.com", + "https://images.unsplash.com", + "https://pbs.twimg.com", + "https://res.cloudinary.com", + "https://utfs.io", + "https://gravatar.com", + + // "https://authjs.dev", + // "https://discordapp.com", + // "https://img.clerk.com", + // "https://img.youtube.com", + `${determineProtocol()}://*.${appDomain()}`, + env.ADDITIONAL_CSP_ORIGINS, + env.NEXT_PUBLIC_APP_URL, + ], + "script-src": scriptSourceDirectives(), +}); + +// Merging base and specific directives +const policies = { + ...baseDirectives(), + ...specificDirectives(), +}; + +// Generating the Content Security Policy string +const corsPolicyString = Object.entries(policies) + .map(([key, sources]) => `${key} ${sources.join(" ")}`) + .join("; "); + +export default corsPolicyString; + +/** + * Details for appDomain and protocol + * ================================== + * + * Determines the application's domain for use in CSP directives. + * + * - First, attempts to extract the domain from env.NEXT_PUBLIC_APP_URL, if it is set. + * - If env.NEXT_PUBLIC_APP_URL is not set, the code then checks the environment. + * - If the NODE_ENV environment variable is not set, is set to 'development', or if + * the VERCEL_URL environment variable is not set, it defaults to "http://localhost:3000". + * - This default is useful for local development environments or in cases where + * VERCEL_URL is not provided in deployment. + * - Otherwise, if in a production environment and VERCEL_URL is set, VERCEL_URL is used. + * - This ensures that appDomain always has a meaningful value based on the application's + * running environment and available environment variables. + * - protocol is then determined based on the current environment, ensuring that the + * application uses the correct protocol ('http' or 'https') for its operation. + * + * REPORT_URL example usage: + * ========================= + * + * Thanks to it you can set up a specialized services for handling types of reports. + * You simply point report-uri directive to their endpoint, and they handle the rest. + * + * Sentry | Cloud Logging | CloudWatch | ELK Stack | Splunk | Graylog | Self-hosted + * + * const specificDirectives = () => ({ + * "report-uri": [ + * "https://the-report-collector.example.com/report-csp-violations", + * ], + * }); + * + * @see https://docs.sentry.io/product/security-policy-reporting + */ diff --git a/addons/cluster/temp/dep-pkg.txt b/addons/cluster/temp/dep-pkg.txt new file mode 100644 index 00000000..b5f95a82 --- /dev/null +++ b/addons/cluster/temp/dep-pkg.txt @@ -0,0 +1,325 @@ +{ + "name": "relivator", + "private": true, + "version": "1.2.6", + "author": { + "name": "Nazar Kornienko", + "email": "blefnk@gmail.com", + "url": "https://github.com/blefnk/relivator-nextjs-template" + }, + "type": "module", + "scripts": { + "appts": "run-s knip typecheck lint format compiler build:i", + "build:i": "turbo build", + "build": "next build", + "compiler": "pnpm dlx react-compiler-healthcheck@latest", + "db:generate": "drizzle-kit generate", + "db:migrate-old": "tsx devtools/unstable/db/migrate.ts", + "db:migrate": "drizzle-kit migrate", + "db:push": "drizzle-kit push", + "db:seed": "tsx devtools/unstable/db/seed-db.ts", + "db:studio": "drizzle-kit studio", + "dev:email": "email dev", + "dev:i": "turbo dev", + "dev": "next dev --turbo", + "email": "email dev --dir src/data/mail -p 3001", + "fix:ts": "typestat --config typestat.json", + "format": "pnpm biome format --write .", + "knip": "dotenv knip", + "latest": "pnpm update --latest && npx nypm add react@canary react-dom@canary next@canary swr@beta next-auth@beta tailwindcss@next @tailwindcss/postcss@next @tailwindcss/cli@next @trpc/server@next @trpc/client@next @trpc/react-query@next", + "lint:standard-fix": "pnpm standard --fix && pnpm ts-standard --fix", + "lint:standard": "pnpm standard && pnpm ts-standard", + "lint:stylelint": "pnpm stylelint src/**/*.css", + "lint:xo-fix": "pnpm xo --plugin=react src --fix", + "lint:xo": "pnpm xo --plugin=react src", + "lint": "pnpm eslint --fix src --ext js,ts,mjs,jsx,tsx,mdx,json && pnpm biome lint --apply .", + "postinstall": "dotenv tsx reliverse.config.ts", + "shadcn": "pnpx shadcn-ui@latest add", + "start": "next start", + "stripe:listen": "stripe listen --forward-to localhost:3000/api/webhooks/stripe --latest", + "test:ava-tsx": "cross-env NODE_OPTIONS=\"--import=tsx\" ava", + "test:ava": "pnpm ava", + "test:jest": "pnpm jest --passWithNoTests", + "test:pw": "pnpm playwright test", + "test": "pnpm test:jest", + "typecheck:watch": "tsc -w", + "typecheck": "tsc --noEmit", + "ui:eslint": "pnpx eslint-flat-config-viewer", + "watch:ts": "tsc -w", + "wt": "pnpx webhookthing@latest --use-pnpm" + }, + "dependencies": { + "@auth/core": "^0.32.0", + "@auth/drizzle-adapter": "^1.2.0", + "@clerk/nextjs": "^5.1.3", + "@clerk/themes": "^2.1.8", + "@clerk/types": "^4.5.1", + "@faire/mjml-react": "^3.3.0", + "@faker-js/faker": "^8.4.1", + "@gsap/react": "^2.1.1", + "@hookform/resolvers": "^3.4.2", + "@inquirer/prompts": "^5.0.5", + "@libsql/client": "0.6.2", + "@liveblocks/client": "^1.12.0", + "@liveblocks/react": "^1.12.0", + "@loglib/tracker": "^0.8.0", + "@mdx-js/loader": "^3.0.1", + "@mdx-js/react": "^3.0.1", + "@million/lint": "1.0.0-rc.20", + "@neondatabase/serverless": "^0.9.3", + "@next/bundle-analyzer": "^14.2.3", + "@next/mdx": "^14.2.3", + "@normy/react-query": "^0.14.2", + "@paralleldrive/cuid2": "^2.2.2", + "@planetscale/database": "^1.18.0", + "@radix-ui/react-accordion": "^1.1.2", + "@radix-ui/react-alert-dialog": "^1.0.5", + "@radix-ui/react-aspect-ratio": "^1.0.3", + "@radix-ui/react-avatar": "^1.0.4", + "@radix-ui/react-checkbox": "^1.0.4", + "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-dropdown-menu": "^2.0.6", + "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-label": "^2.0.2", + "@radix-ui/react-menubar": "^1.0.4", + "@radix-ui/react-navigation-menu": "^1.1.4", + "@radix-ui/react-popover": "^1.0.7", + "@radix-ui/react-scroll-area": "^1.0.5", + "@radix-ui/react-select": "^2.0.0", + "@radix-ui/react-separator": "^1.0.3", + "@radix-ui/react-slider": "^1.1.2", + "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-switch": "^1.0.3", + "@radix-ui/react-tabs": "^1.0.4", + "@radix-ui/react-toast": "^1.1.5", + "@radix-ui/react-tooltip": "^1.0.7", + "@radix-ui/themes": "^3.0.5", + "@react-email/head": "^0.0.9", + "@react-email/html": "^0.0.8", + "@react-email/img": "^0.0.8", + "@react-email/tailwind": "^0.0.18", + "@reliverse/core": "npm:@jsr/reliverse__core@^0.1.0", + "@simplewebauthn/browser": "^10.0.0", + "@simplewebauthn/server": "^10.0.0", + "@stripe/react-stripe-js": "^2.7.1", + "@stripe/stripe-js": "^3.5.0", + "@t3-oss/env-nextjs": "^0.10.1", + "@tanstack/react-query": "^5.40.0", + "@tanstack/react-query-devtools": "^5.40.0", + "@tanstack/react-query-next-experimental": "^5.40.0", + "@tanstack/react-table": "^8.17.3", + "@trpc/client": "11.0.0-rc.390", + "@trpc/react-query": "11.0.0-rc.390", + "@trpc/server": "11.0.0-rc.390", + "@udecode/plate-autoformat": "^33.0.0", + "@udecode/plate-basic-marks": "^33.0.3", + "@udecode/plate-block-quote": "^33.0.0", + "@udecode/plate-break": "^33.0.0", + "@udecode/plate-code-block": "^33.0.2", + "@udecode/plate-common": "^33.0.4", + "@udecode/plate-heading": "^33.0.3", + "@udecode/plate-highlight": "^33.0.0", + "@udecode/plate-horizontal-rule": "^33.0.0", + "@udecode/plate-indent-list": "^33.0.3", + "@udecode/plate-list": "^33.0.3", + "@udecode/plate-media": "^33.0.2", + "@udecode/plate-node-id": "^33.0.0", + "@udecode/plate-normalizers": "^33.0.3", + "@udecode/plate-paragraph": "^33.0.0", + "@udecode/plate-reset-node": "^33.0.0", + "@udecode/plate-select": "^33.0.0", + "@udecode/plate-table": "^33.0.7", + "@udecode/plate-trailing-block": "^33.0.7", + "@uidotdev/usehooks": "^2.4.1", + "@uploadthing/react": "^6.6.0", + "axios": "^1.7.2", + "babel-plugin-react-compiler": "0.0.0-experimental-c23de8d-20240515", + "better-sqlite3": "^11.0.0", + "class-variance-authority": "^0.7.0", + "client-only": "^0.0.1", + "clsx": "^2.1.1", + "cmdk": "^1.0.0", + "consola": "^3.2.3", + "cookies-next": "^4.2.1", + "cropperjs": "^1.6.2", + "dayjs": "^1.11.11", + "deepmerge": "^4.3.1", + "drizzle-orm": "^0.31.0", + "drizzle-zod": "^0.5.1", + "embla-carousel-react": "^8.1.3", + "flag-icons": "^7.2.3", + "flowbite-react": "^0.9.0", + "geist": "^1.3.0", + "globals": "^15.3.0", + "gsap": "^3.12.5", + "lucide-react": "^0.383.0", + "mdx": "^0.3.1", + "million": "^3.1.9", + "nanoid": "^5.0.7", + "next": "15.0.0-canary.8", + "next-auth": "5.0.0-beta.19", + "next-intl": "^3.14.1", + "next-themes": "^0.3.0", + "nextjs-google-analytics": "^2.3.3", + "nodemailer": "^6.9.13", + "pg": "^8.11.5", + "postgres": "^3.4.4", + "radash": "^12.1.0", + "react": "19.0.0-rc-9598c41a20-20240603", + "react-cropper": "^2.3.3", + "react-day-picker": "^8.10.1", + "react-dom": "19.0.0-rc-9598c41a20-20240603", + "react-dropzone": "^14.2.3", + "react-hook-form": "^7.51.5", + "react-medium-image-zoom": "^5.2.4", + "react-wrap-balancer": "^1.1.1", + "remark-gfm": "^4.0.0", + "resend": "^3.2.0", + "semver": "^7.6.2", + "slate": "^0.103.0", + "slate-history": "^0.100.0", + "slate-hyperscript": "^0.100.0", + "slate-react": "^0.104.0", + "string-ts": "^2.1.1", + "stripe": "^15.9.0", + "superjson": "^2.2.1", + "swr": "2.2.6-beta.4", + "tailwind-variants": "^0.2.1", + "tailwindcss-animate": "^1.0.7", + "turbo": "^1.13.3", + "uploadthing": "^6.12.0", + "user-agent-data-types": "^0.4.2", + "uuid": "^9.0.1", + "vaul": "^0.9.1", + "winston": "^3.13.0", + "zod": "^3.23.8", + "zustand": "^4.5.2" + }, + "devDependencies": { + "@ava/typescript": "^5.0.0", + "@baselime/node-opentelemetry": "^0.5.8", + "@biomejs/biome": "^1.7.3", + "@commitlint/types": "^19.0.3", + "@eslint-react/eslint-plugin": "^1.5.14", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "^9.4.0", + "@next/eslint-plugin-next": "^14.2.3", + "@opentelemetry/api": "^1.8.0", + "@stylistic/eslint-plugin": "^2.1.0", + "@tailwindcss/cli": "4.0.0-alpha.15", + "@tailwindcss/postcss": "4.0.0-alpha.15", + "@tailwindcss/typography": "^0.5.13", + "@testing-library/jest-dom": "^6.4.5", + "@total-typescript/ts-reset": "^0.5.1", + "@types/better-sqlite3": "^7.6.10", + "@types/jest": "^29.5.12", + "@types/mdx": "^2.0.13", + "@types/node": "^20.14.0", + "@types/pg": "^8.11.6", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@types/semver": "^7.5.8", + "@types/uuid": "^9.0.8", + "ava": "^6.1.3", + "cross-env": "^7.0.3", + "dotenv": "^16.4.5", + "dotenv-cli": "^7.4.2", + "drizzle-kit": "^0.22.1", + "eslint": "^9.4.0", + "eslint-plugin-drizzle": "^0.2.3", + "eslint-plugin-import-x": "^0.5.1", + "eslint-plugin-mdx": "^3.1.5", + "eslint-plugin-no-relative-import-paths": "^1.5.4", + "eslint-plugin-perfectionist": "^2.10.0", + "eslint-plugin-react": "^7.34.2", + "eslint-plugin-react-compiler": "0.0.0-experimental-c8b3f72-20240517", + "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-react-refresh": "^0.4.7", + "eslint-plugin-react-server-components": "^1.2.0", + "eslint-plugin-sort": "^3.0.2", + "eslint-plugin-sort-exports": "^0.9.1", + "eslint-plugin-unicorn": "^53.0.0", + "jest": "^29.7.0", + "jest-extended": "^4.0.2", + "jiti": "^1.21.0", + "knip": "^5.17.4", + "mysql2": "^3.10.0", + "next-secure-headers": "^2.2.0", + "npm-run-all2": "^6.2.0", + "postcss": "^8.4.38", + "standard": "^17.1.0", + "stylelint": "^16.6.1", + "stylelint-config-css-modules": "^4.4.0", + "stylelint-config-standard-scss": "^13.1.0", + "stylelint-scss": "^6.3.1", + "tailwind-merge": "^2.3.0", + "tailwindcss": "4.0.0-alpha.15", + "ts-node": "^10.9.2", + "ts-standard": "^12.0.2", + "tsx": "^4.11.2", + "typescript": "5.5.0-dev.20240603", + "typescript-eslint": "^7.12.0", + "xo": "^0.58.0" + }, + "pnpm": { + "allowedDeprecatedVersions": { + "@babel/plugin-proposal-private-methods": "*", + "abab": "*", + "are-we-there-yet": "*", + "domexception": "*", + "eslint-config-standard-with-typescript": "*", + "gauge": "*", + "glob": "*", + "inflight": "*", + "npmlog": "*", + "rimraf": "*" + }, + "overrides": { + "@antfu/eslint-config>eslint": "*", + "@clerk/clerk-react>eslint": "*", + "@contentlayer/core>esbuild": "*", + "@decs/typeschema>@effect/schema": "*", + "@decs/typeschema>@sinclair/typebox": "*", + "@decs/typeschema>effect": "*", + "@limegrass/eslint-plugin-import-alias>eslint": "*", + "@auth/core>@simplewebauthn/browser": "*", + "@auth/core>@simplewebauthn/server": "*", + "next-auth>@simplewebauthn/browser": "*", + "next-auth>@simplewebauthn/server": "*", + "@opentelemetry/core>@opentelemetry/api": "*", + "@opentelemetry/otlp-transformer>@opentelemetry/api": "*", + "@opentelemetry/resources>@opentelemetry/api": "*", + "@opentelemetry/sdk-logs>@opentelemetry/api": "*", + "@opentelemetry/sdk-metrics>@opentelemetry/api": "*", + "@opentelemetry/sdk-trace-base>@opentelemetry/api": "*", + "@tanstack/eslint-plugin-query>eslint": "*", + "eslint-config-airbnb-base": "*", + "eslint-config-airbnb": "*", + "eslint-config-airbnb>eslint": "*", + "eslint-config-next>eslint": "*", + "eslint-interactive>eslint": "*", + "eslint-plugin-deprecation>eslint": "*", + "eslint-plugin-functional>eslint": "*", + "eslint-plugin-i>eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import>eslint": "*", + "eslint-plugin-jest-extended": "*", + "eslint-plugin-jest-extended>eslint": "*", + "eslint-plugin-jsx-a11y>eslint": "*", + "eslint-plugin-promise>eslint": "*", + "eslint-plugin-react-hooks>eslint": "*", + "eslint-plugin-react>eslint": "*", + "eslint-plugin-redundant-undefined>eslint": "*", + "eslint-plugin-unused-imports>eslint": "*", + "eslint": "*", + "flowbite-react>tailwindcss": "*", + "graphql": "*", + "next": "$next", + "react-dom": "$react-dom", + "react": "$react", + "standard>eslint": "*", + "ts-standard>eslint": "*", + "xo>eslint": "*" + } + } +} diff --git a/addons/cluster/temp/drizzle.config.txt b/addons/cluster/temp/drizzle.config.txt new file mode 100644 index 00000000..94f86605 --- /dev/null +++ b/addons/cluster/temp/drizzle.config.txt @@ -0,0 +1,216 @@ +// 🔴 DEPRECATED AND POSSIBLY WILL BE REMOVED IN RELIVATOR 1.3.0 🔴 || +// ================================================================= || + +/** + * [drizzle-orm-mono] Drizzle ORM Configuration + * + * This script configures drizzle-orm with environment-specific settings, + * including database connection details. The configuration relies on + * env vars for setting the database provider and connection string. + * + * @see https://github.com/bs-oss/drizzle-orm-mono + * @see https://orm.drizzle.team/kit-docs/config-reference + * @see https://discord.com/channels/1043890932593987624/1043890932593987627/1153940001885794304 + */ + +// import * as dotenv from "dotenv"; +import type { Config } from "drizzle-kit"; +import { databasePrefix } from "~/../reliverse.config.ts"; + +import { env } from "./src/env"; + +// dotenv.config(); +// dotenv.config({ path: ".env" }); + +// if (!env.NEXT_PUBLIC_DB_PROVIDER || !env.DATABASE_URL) +// throw new Error( +// "NEXT_PUBLIC_DB_PROVIDER or DATABASE_URL is not set in environment variables.", +// ); + +// Add the ssl query parameter if it's missing +// let csMysql: string = addQueryParamIfMissed( +// env.DATABASE_URL || "", +// "ssl", +// JSON.stringify({ rejectUnauthorized: true }), +// ); +// let csPgsql: string = addQueryParamIfMissed( +// env.DATABASE_URL || "", +// "sslmode", +// "require", +// ); + +// Connection strings for MySQL and PostgreSQL +const csMysql = env.DATABASE_URL; +const csPgsql = env.DATABASE_URL; +// const csMysql = `${env.DATABASE_URL}?ssl={"rejectUnauthorized":true}`; +// const csPgsql = `${env.DATABASE_URL}?sslmode=require`; + +// Initialize configuration variables +// type MysqlCredentials = { url: string }; +// type PgsqlCredentials = { url: string }; +// let dbCredentials: MysqlCredentials | PgsqlCredentials; +let dialect: "mysql" | "postgresql" | "sqlite"; +let schema: string; +let out: string; +let dbProvider = env.NEXT_PUBLIC_DB_PROVIDER; + +/** + * Configure this based on the database provider. + * Feel free to add/remove/edit things if needed. + */ +try { + // Set default DB provider based on DATABASE_URL + // if NEXT_PUBLIC_DB_PROVIDER is not specified + if (!dbProvider) { + const databaseUrl = env.DATABASE_URL || ""; + if (databaseUrl?.startsWith("mysql://")) { + dbProvider = "private-mysql"; + csMysql = env.DATABASE_URL; + } else if (databaseUrl?.startsWith("postgres://")) { + dbProvider = "private-postgres"; + csPgsql = env.DATABASE_URL; + } + } + + switch (dbProvider) { + case "private-mysql": + case "planetscale": + dialect = "mysql"; + // out = "drizzle/mysql"; + // dbCredentials = { url: csMysql }; + schema = "./src/db/schema/mysql.ts"; + // console.info("✓ MySQL triggered"); + break; + case "railway": + if (env.DATABASE_URL?.startsWith("mysql://")) { + dialect = "mysql"; + // out = "drizzle/mysql"; + // dbCredentials = { url: csMysql }; + schema = "./src/db/schema/mysql.ts"; + } else if (env.DATABASE_URL?.startsWith("postgres://")) { + dialect = "postgresql"; + // out = "drizzle/pgsql"; + // dbCredentials = { url: csPgsql }; + schema = "./src/db/schema/pgsql.ts"; + } else { + throw new Error( + `❌ Unsupported DATABASE_URL for NEXT_PUBLIC_DB_PROVIDER '${dbProvider}'.\ + Verify the environment configuration.`, + ); + } + break; + case "vercel": + case "neon": + case "private-postgres": + dialect = "postgresql"; + // out = "drizzle/pgsql"; + // dbCredentials = { url: csPgsql }; + schema = "./src/db/schema/pgsql.ts"; + // console.info("✓ PostgreSQL triggered"); + break; + default: + throw new Error( + `❌ Unsupported NEXT_PUBLIC_DB_PROVIDER '${dbProvider}'.\ + Verify the environment configuration.`, + ); + } +} catch (error) { + if (error instanceof Error) { + // Only the error message will be shown to the user + console.error(error.message); + // Exits the process with a failure code + process.exit(1); + } else { + // If for any reason something else was thrown that wasn't an Error handles it + console.error("❌ An unexpected error occurred:", error); + // Exit with a failure mode + process.exit(1); + } +} + +// type TMysqlProvider = "railway" | "planetscale"; +// type TPgsqlProvider = "vercel" | "neon"; +// const mysqlProvider: TMysqlProvider = "railway"; +// const pgsqlProvider: TPgsqlProvider = "neon"; + +// Drizzle Config +export default { + dialect: "postgresql", // postgresql OR mysql + out: `./drizzle/${dbProvider === "neon" ? "pgsql" : "mysql"}`, // pgsql: neon|vercel|railway OR mysql: railway|planetscale + dbCredentials: { url: env.DATABASE_URL }, + tablesFilter: [`${databasePrefix}_*`], + schema, +} satisfies Config; + +// ===== [🚧 TODO SECTION 🚧] ==================================== + +// todo: we need to find the way to hook executed `pnpm {dialect}:{cmd}` + +// todo: unfinished, checks implementation for the required directories +// import { existsSync } from "fs"; +// import { join } from "pathe"; +// Check if the required directory exists, throw an error if not +/* try { + const drizzleDirPath = join(process.cwd(), out); + if (!existsSync(drizzleDirPath)) { + throw new Error( + "💡 The required files in `drizzle` directory do not exist.\ + Please execute `pnpm mysql:generate` (PlanetScale provider),\ + or `pnpm pg:generate` (Neon provider), to generate the necessary\ + files. Afterward, you may retry the previous command.", + ); + } +} catch (error) { + if (error instanceof Error) { + console.error(error.message); + } else { + // This block will handle non-Error objects thrown, which is a rare case + console.error("An unexpected error occurred:", error); + } + // Exit with a failure mode + process.exit(1); +} */ + +// todo: unfinished, jest fails upon nextjs build +/* type ProviderUrlPrefixes = { + // Type for the provider names and their corresponding URL prefixes + [key in "postgres" | "mysql"]?: string; +}; +// Map database providers to their expected URL prefixes +const providerUrlPrefixes: ProviderUrlPrefixes = { + postgres: "postgres://", + mysql: "mysql://", +}; +// Validate essential environment variables and check for URL prefix +if (NEXT_PUBLIC_DB_PROVIDER && env.DATABASE_URL) { + const expectedPrefix = + providerUrlPrefixes[ + NEXT_PUBLIC_DB_PROVIDER as + | "planetscale" + | "neon" + | "vercel" + | "railway" + ]; + if (expectedPrefix) { + if (!env.DATABASE_URL.startsWith(expectedPrefix)) { + console.error( + `💡 Connection error: The DATABASE_URL does not match the\ + expected format for provider '${NEXT_PUBLIC_DB_PROVIDER}'.\ + Please check the configuration.`, + ); + process.exit(1); + } + } else { + console.error( + `💡 Unknown NEXT_PUBLIC_DB_PROVIDER '${NEXT_PUBLIC_DB_PROVIDER}'.\ + Please check the configuration.`, + ); + process.exit(1); + } +} else { + console.error( + "💡 Essential environment variables are missing. Ensure\ + NEXT_PUBLIC_DB_PROVIDER and DATABASE_URL are set.", + ); + process.exit(1); +} */ diff --git a/addons/cluster/temp/error.tsx.txt b/addons/cluster/temp/error.tsx.txt new file mode 100644 index 00000000..4be78d1a --- /dev/null +++ b/addons/cluster/temp/error.tsx.txt @@ -0,0 +1,91 @@ +// 🟡 DEPRECATED AND POSSIBLY WILL BE UPDATED IN RELIVATOR 1.3.0 🟡 || +// ================================================================= || + +"use client"; + +import Link from "next/link"; +import { config } from "@reliverse/core"; +import { cn } from "~/utils"; +import { Balancer } from "react-wrap-balancer"; + +import { buttonVariants } from "~/components/primitives"; +import { Card } from "~/components/Primitives/ui/card"; +import { Button } from "~/components/Primitives/ui/Button"; +import PageLayout from "~/components/wrappers/page-layout"; + +export default function Error({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + return ( + <PageLayout title="Something went wrong 😅"> + <Balancer + as="p" + className="mx-auto mt-4 !block leading-normal text-muted-foreground sm:text-lg sm:leading-7" + > + A critical error has occurred. Please check the console for more + information or contact support if the problem persists. + </Balancer> + {error?.stack && ( + <Balancer as="p"> + <details className="mt-4"> + <summary className="cursor-pointer text-sm text-muted-foreground"> + Show stack trace + </summary> + <pre className="overflow-x-auto p-2 text-xs text-muted-foreground max-w-lg"> + {error.stack} + </pre> + </details> + </Balancer> + )} + <Balancer + as="p" + className="flex justify-center w-full mx-auto mt-4 leading-normal text-muted-foreground sm:text-lg sm:leading-7" + > + <Card className="px-4 py-2 bg-destructive text-destructive-foreground text-base"> + {error.message} + </Card> + </Balancer> + <div className="flex justify-center w-full"> + <Button + className="mt-4 max-w-lg w-full" + onClick={() => reset()} + variant="outline" + > + Try again + </Button> + </div> + <div className="flex justify-center w-full"> + <Link + href="/" + className={cn( + buttonVariants({ + size: "default", + variant: "secondary", + }), + "mt-4 max-w-lg w-full", + )} + > + Go to homepage + </Link> + </div> + <div className="flex justify-center w-full"> + <Link + href={config.social.discord} + className={cn( + buttonVariants({ + size: "default", + variant: "secondary", + }), + "mt-4 max-w-lg w-full", + )} + > + {config.framework.name}'s Discord + </Link> + </div> + </PageLayout> + ); +} diff --git a/addons/cluster/temp/eslint.config.deprecated.a.txt b/addons/cluster/temp/eslint.config.deprecated.a.txt new file mode 100644 index 00000000..031712cc --- /dev/null +++ b/addons/cluster/temp/eslint.config.deprecated.a.txt @@ -0,0 +1,742 @@ +// 🔴 DEPRECATED AND POSSIBLY WILL BE REMOVED IN RELIVATOR 1.3.0 🔴 || +// ================================================================= || + +/** + * Blefnk's ESLint Configuration for Relivator 1.2.0 + * ================================================= + * + * Remember to run `>ESLint: Restart ESLint Server` + * command after making changes and the file saving. + * Tip: Open `>Keyboard Shortcuts` and bind restart. + * + * When using ESLint VSCode extension, make sure you + * have `Use Flat Config` option enabled in settings. + * Bonus tip: When using Relivator, use `pnpm appts`. + * + * Note: antfu already includes the following plugins: + * typescript, stylistic, perfectionist, jsonc, react, + * unicorn, unocss, vue, yaml, toml, jsdoc, markdown. + * Go to `export default antfu` to see actual config. + * + * @see https://github.com/antfu/eslint-config#antfueslint-config + * @see https://eslint.org/docs/latest/use/configure/configuration-files-new + * @see https://github.com/blefnk/relivator-nextjs-template#readme <== get config updates + */ + +import antfu from "@antfu/eslint-config"; +import { FlatCompat } from "@eslint/eslintrc"; +import eslintJsPlugin from "@eslint/js"; +import nextPlugin from "@next/eslint-plugin-next"; +import stylisticPlugin from "@stylistic/eslint-plugin"; +import * as airbnbBestPracticesConfig from "eslint-config-airbnb-base/rules/best-practices"; +import * as airbnbErrorsConfig from "eslint-config-airbnb-base/rules/errors"; +import * as airbnbES6Config from "eslint-config-airbnb-base/rules/es6"; +import * as airbnbNodeConfig from "eslint-config-airbnb-base/rules/node"; +import * as airbnbStyleConfig from "eslint-config-airbnb-base/rules/style"; +import * as airbnbVariablesConfig from "eslint-config-airbnb-base/rules/variables"; +import commentsPlugin from "eslint-plugin-eslint-comments"; +import functionalPlugin from "eslint-plugin-functional"; +import jestPlugin from "eslint-plugin-jest"; +import jsxA11yPlugin from "eslint-plugin-jsx-a11y"; +import noSecretsPlugin from "eslint-plugin-no-secrets"; +import promisePlugin from "eslint-plugin-promise"; +import reactPlugin from "eslint-plugin-react"; +import reactHooksPlugin from "eslint-plugin-react-hooks"; +import reactPluginConfigsRecommended from "eslint-plugin-react/configs/recommended"; +import redundantUndefinedPlugin from "eslint-plugin-redundant-undefined"; +// @ts-expect-error - no types // todo: tw v3 only +import tailwindcss from "eslint-plugin-tailwindcss"; +import tsdocPlugin from "eslint-plugin-tsdoc"; +import writeGoodCommentsPlugin from "eslint-plugin-write-good-comments"; +import xssPlugin from "eslint-plugin-xss"; + +// @type {import('@typescript-eslint/utils').TSESLint.FlatConfig.ConfigFile} */ +// const config = []; +// export default tseslint.config(...config); + +const compat = new FlatCompat(); + +// @type {import('@typescript-eslint/utils').TSESLint.FlatConfig.ConfigFile} */ +// TODO: With `export default []`, ESLint doesn't work, but with `tseslint.config`, VSCode hot-reload works slowly looks like + +export default antfu( + { + ignores: [".next", "build"], + typescript: { tsconfigPath: "tsconfig.json" }, + stylistic: { quotes: "double", semi: true }, + settings: { react: { version: "detect" } }, + formatters: { css: true }, + react: true, + vue: false, + plugins: { + "redundant-undefined": redundantUndefinedPlugin, + "write-good-comments": writeGoodCommentsPlugin, + "eslint-comments": commentsPlugin, + "no-secrets": noSecretsPlugin, + "jsx-a11y": jsxA11yPlugin, + "xss": xssPlugin, + "react": reactPlugin, + "@next/next": nextPlugin, + "promise": promisePlugin, + "@stylistic": stylisticPlugin, + "functional": functionalPlugin, + "react-hooks": reactHooksPlugin, + "tailwindcss": tailwindcssPlugin, + }, + rules: { + ...nextPlugin.configs.recommended.rules, + ...reactPluginConfigsRecommended.rules, + ...promisePlugin.configs.recommended.rules, + ...eslintJsPlugin.configs.recommended.rules, + ...tailwindcssPlugin.configs.recommended.rules, + ...stylisticPlugin.configs["recommended-flat"].rules, + ...reactHooksPlugin.configs.recommended.rules, + ...xssPlugin.configs.recommended.rules, + ...airbnbBestPracticesConfig.rules, + ...airbnbVariablesConfig.rules, + ...airbnbErrorsConfig.rules, + ...airbnbStyleConfig.rules, + ...airbnbNodeConfig.rules, + ...airbnbES6Config.rules, + "antfu/consistent-list-newline": "off", + "antfu/if-newline": "off", + "antfu/top-level-function": "off", + "array-bracket-newline": ["off", "consistent"], + "array-bracket-spacing": ["off", "never"], + "arrow-body-style": ["off", "always"], + "arrow-parens": "off", + "arrow-spacing": "off", + "block-scoped-var": "off", + "block-spacing": ["off", "always"], + "brace-style": ["off", "1tbs", { "allowSingleLine": true }], + "camelcase": "off", + "comma-dangle": ["off", "only-multiline"], + "comma-spacing": ["off", { "before": false, "after": true }], + "comma-style": ["off", "last"], + "complexity": ["off", 63], + "curly": "off", + "default-case-last": "off", + "default-case": "off", + "default-param-last": ["off"], + "dot-location": ["off", "property"], + "dot-notation": "off", + "eol-last": ["off", "always"], + "eqeqeq": ["off", "always"], + "eslint-plugin-jsx-a11y/blob": "off", + "func-names": "off", + "func-style": ["off", "declaration"], + "function-call-argument-newline": ["off", "consistent"], + "function-paren-newline": ["off", "consistent"], + "global-require": "off", + "implicit-arrow-linebreak": "off", + "import/no-absolute-path": "off", + "import/no-cycle": "off", + "import/no-duplicates": "off", + "import/no-dynamic-require": "off", + "import/no-extraneous-dependencies": "off", + "import/no-mutable-exports": "off", + "import/no-named-as-default": "off", + "import/order": ["off"], + "import/prefer-default-export": "off", + "indent": ["off", 2], + "jsdoc/check-access": "off", + "jsdoc/check-alignment": "off", + "jsdoc/check-indentation": "off", + "jsdoc/check-param-names": "off", + "jsdoc/check-syntax": "off", + "jsdoc/check-tag-names": "off", + "jsdoc/no-blank-blocks": "off", + "jsdoc/no-types": "off", + "jsdoc/no-undefined-types": "off", + "jsdoc/require-asterisk-prefix": "off", + "jsdoc/require-description": "off", + "jsdoc/require-hyphen-before-param-description": "off", + "jsdoc/require-param-type": "off", + "jsdoc/require-param": ["off", { checkDestructuredRoots: false }], + "jsdoc/require-returns-type": "off", + "jsdoc/require-returns": "off", + "jsdoc/require-throws": "off", + "jsdoc/sort-tags": "off", + "jsdoc/tag-lines": ["off", "never", { startLines: 1 }], + "jsonc/sort-keys": "off", + "jsx-a11y/anchor-has-content": "off", + "jsx-a11y/anchor-is-valid": "off", + "jsx-a11y/heading-has-content": "off", + "key-spacing": "off", + "linebreak-style": "off", + "lines-around-directive": "off", + "max-lines-per-function": ["off", 437], + "max-nested-callbacks": ["off", 4], + "max-statements": ["off", 76], + "new-cap": "off", + "new-parens": "off", + "no-alert": "off", + "no-array-constructor": "off", + "no-await-in-loop": "off", + "no-bitwise": "off", + "no-case-declarations": "off", + "no-confusing-arrow": "off", + "no-console": "off", + "no-constant-condition": "off", + "no-duplicate-selectors": "off", + "no-else-return": "off", + "no-eval": "off", + "no-irregular-whitespace": "warn", + "no-lonely-if": "off", + "no-multi-assign": "off", + "no-multi-spaces": ["off", { "ignoreEOLComments": true }], + "no-multi-str": "off", + "no-nested-ternary": "off", + "no-param-reassign": "off", + "no-plusplus": "off", + "no-promise-executor-return": "off", + "no-redeclare": "off", + "no-restricted-globals": "off", + "no-return-assign": "off", + "no-return-await": "off", + "no-tabs": "off", + "no-template-curly-in-string": "off", + "no-trailing-spaces": "off", + "no-undef": "off", + "no-unneeded-ternary": "off", + "no-unused-vars": "off", + "no-use-before-define": "off", + "no-useless-return": "off", + "no-var": "off", + "no-void": "off", + "no-whitespace-before-property": "off", + "node/prefer-global/process": "off", + "nonblock-statement-body-position": "off", + "object-curly-newline": ["off", { consistent: true }], + "object-curly-spacing": ["off", "always"], + "object-shorthand": "off", + "one-var": "off", + "operator-assignment": "off", + "operator-linebreak": "off", + "padded-blocks": "off", + "perfectionist/sort-array-includes": "off", + "perfectionist/sort-imports": "off", + "perfectionist/sort-interfaces": "off", + "perfectionist/sort-jsx-props": "off", + "perfectionist/sort-named-exports": "off", + "perfectionist/sort-named-imports": "off", + "perfectionist/sort-object-types": "off", + "perfectionist/sort-objects": "off", + "perfectionist/sort-union-types": "off", + "prefer-arrow-callback": "off", + "prefer-const": "off", + "prefer-destructuring": "off", + "prefer-exponentiation-operator": "off", + "prefer-rest-params": "off", + "prefer-template": "off", + "quote-props": "off", + "quotes": ["off", "single", { "allowTemplateLiterals": true }], + "radix": "off", + "react-hooks/rules-of-hooks": "off", + "react/button-has-type": "off", + "react/destructuring-assignment": "off", + "react/display-name": "off", + "react/function-component-definition": "off", + "react/jsx-boolean-value": "off", + "react/jsx-curly-brace-presence": "off", + "react/jsx-fragments": "off", + "react/jsx-max-depth": ["off", { max: 7 }], + "react/jsx-no-bind": "off", + "react/jsx-no-constructed-context-values": "off", + "react/jsx-no-leaked-render": ["off", { validStrategies: ["ternary"] }], + "react/jsx-no-script-url": "off", + "react/jsx-one-expression-per-line": "off", + "react/jsx-props-no-spreading": "off", + "react/jsx-sort-props": "off", + "react/jsx-uses-react": "off", + "react/jsx-uses-vars": "off", + "react/no-array-index-key": "off", + "react/no-children-prop": "off", + "react/no-danger-with-children": "off", + "react/no-danger": "off", + "react/no-typos": "off", + "react/no-unescaped-entities": "off", + "react/no-unknown-property": "off", + "react/no-unstable-nested-components": "off", + "react/no-unused-prop-types": "off", + "react/prefer-stateless-function": "off", + "react/prop-types": "off", + "react/react-in-jsx-scope": "off", + "react/require-default-props": "off", + "react/self-closing-comp": "off", + "rest-spread-spacing": ["off", "never"], + "rules/anchor-is-valid": "off", + "semi-spacing": "off", + "semi-style": ["off", "last"], + "semi": "off", + "space-before-blocks": "off", + "space-in-parens": ["off", "never"], + "space-unary-ops": "off", + "spaced-comment": "off", + "style/arrow-parens": "off", + "style/brace-style": "off", + "style/comma-dangle": "off", + "style/eol-last": "off", + "style/indent-binary-ops": "off", + "style/indent": "off", + "style/jsx-closing-tag-location": "off", + "style/jsx-curly-newline": "off", + "style/jsx-indent-props": "off", + "style/jsx-indent": "off", + "style/jsx-one-expression-per-line": "off", + "style/jsx-wrap-multilines": "off", + "style/member-delimiter-style": "off", + "style/multiline-ternary": "off", + "style/no-multi-spaces": "off", + "style/no-multiple-empty-lines": "off", + "style/no-tabs": "off", + "style/no-trailing-spaces": "off", + "style/operator-linebreak": "off", + "style/padded-blocks": "off", + "style/quote-props": "off", + "style/quotes": "off", + "style/semi-spacing": "off", + "style/semi": "off", + "style/spaced-comment": "off", + "switch-colon-spacing": "off", + "tailwindcss/no-custom-classname": "off", + "template-curly-spacing": ["off", "never"], + "unused-imports/no-unused-imports": "off", + "unused-imports/no-unused-vars": "off", + "write-good-comments/write-good-comments": "off", + "xss/no-location-href-assign": "off", + "xss/no-mixed-html": "off", + "yaml/block-sequence": "off", + "yaml/plain-scalar": "off", + "computed-property-spacing": [ + "off", + "never", + { "enforceForClassMembers": true }, + ], + "sort-imports": [ + "off", + { "ignoreCase": true, "ignoreDeclarationSort": false }, + ], + "space-before-function-paren": [ + "off", + { "anonymous": "always", "named": "never", "asyncArrow": "always" }, + ], + "jsdoc/require-jsdoc": [ + "off", + { + publicOnly: true, + require: { + FunctionDeclaration: true, + FunctionExpression: true, + ArrowFunctionExpression: true, + ClassDeclaration: true, + ClassExpression: true, + MethodDefinition: true, + }, + contexts: [ + "VariableDeclaration", + "TSTypeAliasDeclaration", + "TSPropertySignature", + ], + enableFixer: true, + }, + ], + "react/jsx-key": [ + "off", + { + checkFragmentShorthand: true, + checkKeyMustBeforeSpread: true, + warnOnDuplicates: true, + }, + ], + "no-multiple-empty-lines": [ + "off", + { "max": 1, "maxBOF": 0, "maxEOF": 1 }, + ], + }, + }, + ...compat.config({ + plugins: [ + "@limegrass/import-alias", + "prefer-arrow-functions", + "no-barrel-files", + "no-secrets", + "jsx-a11y", + "drizzle", + "react-refresh", + "@tanstack/query", + ], + extends: [ + "plugin:@tanstack/eslint-plugin-query/recommended", + "plugin:deprecation/recommended", + "plugin:jsx-a11y/recommended", + "plugin:drizzle/recommended", + ], + rules: { + "@limegrass/import-alias/import-alias": "off", + "@tanstack/query/exhaustive-deps": "off", + "@tanstack/query/no-rest-destructuring": "off", + "@tanstack/query/stable-query-client": "off", + "deprecation/deprecation": "off", + "drizzle/enforce-delete-with-where": "off", + "drizzle/enforce-update-with-where": "off", + "no-barrel-files/no-barrel-files": "off", + "no-secrets/no-secrets": "off", + "react-refresh/only-export-components": "off", + "prefer-arrow-functions/prefer-arrow-functions": [ + "off", + { + "allowNamedFunctions": false, + "classPropertiesAllowed": false, + "disallowPrototype": false, + "returnStyle": "unchanged", + "singleReturnOnly": false, + }, + ], + }, + }), + { + files: ["**/*.?(*)ts?(x)", ".test.ts(x)"], + plugins: { + "eslint-plugin-tsdoc": tsdocPlugin, + "eslint-plugin-jest": jestPlugin, + }, + rules: { + "jsx-a11y/heading-has-content": "off", + "test/consistent-test-it": "off", + "tsdoc/syntax": "off", + "@typescript-eslint/space-before-function-paren": [ + "off", + { + "asyncArrow": "always", + "anonymous": "always", + "named": "never", + }, + ], + "@typescript-eslint/prefer-literal-enum-member": [ + "off", + { "allowBitwiseExpressions": true }, + ], + }, + }, +); + + +// //////////////////////////////////////////////////////////////// + +// import globals from "globals"; +// import react from "eslint-plugin-react/configs/recommended.js"; +// import path from "pathe"; +// import { fileURLToPath } from "url"; +// import { FlatCompat } from "@eslint/eslintrc"; +// import js from "@eslint/js"; +// import tseslint from "typescript-eslint"; +// Mimic CommonJS variables to be used in ES module +// const esModuleFilename = fileURLToPath(import.meta.url); +// const esModuleDirname = path.dirname(esModuleFilename); +// @see https://eslint.org/docs/latest/use/configure/migration-guide#using-eslintrc-configs-in-flat-config +// const compat = new FlatCompat({ +// baseDirectory: esModuleDirname, +// recommendedConfig: js.configs.recommended, +// }); +// /* export default [ +// { +// // languageOptions: { +// // globals: { ...globals.browser, ...globals.node }, +// // parser: tseslint.parser, +// // parserOptions: { +// // project: ["./tsconfig.json"], +// // tsconfigRootDir: esModuleDirname, +// // }, +// // }, +// }, +// // { ignores: [".github", "build", "dist", "node_modules"] }, +// { +// languageOptions: { +// globals: { ...globals.browser, ...globals.node }, +// parser: tseslint.parser, +// parserOptions: { +// project: "./tsconfig.json", +// // project: ["./tsconfig.json"], +// // tsconfigRootDir: esModuleDirname, +// }, +// }, +// }, +// // ...compat.extends("xo-typescript"), +// // ...tseslint.configs.recommendedTypeChecked, +// // ...tseslint.configs.stylisticTypeChecked, +// // react, +// ]; */ +// const config = tseslint.config( +// { +// ignores: ["build/", "dist/", "node_modules/"], +// }, +// eslint.configs.recommended, +// ...tseslint.configs.recommendedTypeChecked, +// { +// files: ["**/*.test.ts"], +// rules: { +// "@typescript-eslint/require-await": "off", +// }, +// }, +// { +// languageOptions: { +// parserOptions: { +// parser: tseslint.parser, +// project: ["./tsconfig.json"], +// tsconfigRootDir: import.meta.dirname, +// }, +// }, +// plugins: {/**/}, +// rules: { +// "@typescript-eslint/no-unused-vars": [0, { argsIgnorePattern: "^_" }], +// }, +// }, +// ); +// export default config; + +// ///////////////////////////////////////////////////////////////////////////// + +// @type {import("eslint").Linter.Config} */ +// const config = { +// extends: [ +// "eslint:recommended", +// "next/core-web-vitals", +// "plugin:perfectionist/recommended-natural", +// ], +// ignorePatterns: ["**/node_modules/", "**/.next/"], +// parserOptions: { project: true }, +// plugins: ["@typescript-eslint", "drizzle", "perfectionist"], +// rules: { +// complexity: ["off", 10], +// "drizzle/enforce-delete-with-where": [ +// "off", +// { +// drizzleObjectName: ["db"], +// }, +// ], +// "drizzle/enforce-update-with-where": [ +// "off", +// { +// drizzleObjectName: ["db"], +// }, +// ], +// eqeqeq: ["off", "smart"], +// "import/extensions": "off", +// "import/namespace": "off", +// "import/no-duplicates": "off", +// "import/no-extraneous-dependencies": [ +// "off", +// { +// devDependencies: true, +// optionalDependencies: false, +// peerDependencies: false, +// }, +// ], +// "import/no-named-as-default": "off", +// "import/no-named-as-default-member": "off", +// "import/no-unresolved": "off", +// "import/prefer-default-export": "off", +// "max-depth": ["off", 3], +// "max-lines": ["off", 300], +// "no-shadow": [ +// "off", +// { +// hoist: "all", +// }, +// ], +// "prefer-arrow/prefer-arrow-functions": [ +// "off", +// { +// classPropertiesAllowed: false, +// disallowPrototype: true, +// singleReturnOnly: false, +// }, +// ], +// "prefer-const": "off", +// "react/no-unescaped-entities": "off", +// "react-hooks/exhaustive-deps": "off", +// "react-hooks/rules-of-hooks": "off", +// }, +// }; +// module.exports = config; + + +/** + * Resources and Inspirations + * @see https://github.com/blefnk/relivator-nextjs-template + * ========================== + * @see https://typescript-eslint.io typescript + * @see https://eslint.org/docs/latest/rules eslint + * @see https://github.com/import-js/eslint-plugin-import#rules import + * @see https://github.com/antfu/eslint-ts-patch/#readme eslint-ts-patch + * @see https://github.com/sindresorhus/eslint-plugin-unicorn#rules unicorn + * @see https://github.com/ArnaudBarre/eslint-plugin-react-refresh react-refresh + * @see https://github.com/art0rz/eslint-plugin-no-barrel-files#rules no-barrel-files + * @see https://github.com/Anparasan3/core-js/blob/master/eslint.config.js inspirations + * @see https://mysticatea.github.io/eslint-plugin-eslint-comments/rules eslint-comments + * @see https://github.com/Limegrass/eslint-plugin-import-alias#configuration import-alias + * @see https://github.com/nirv-ai/tinkerbuntune/blob/develop/eslint.config.js inspirations + * @see https://github.com/eslint-functional/eslint-plugin-functional#supported-rules functional + * @see https://github.com/a-tarasyuk/eslint-plugin-redundant-undefined#usage redundant-undefined + */ + + +// //////////////////////////////////////////////////////////////// + +// import globals from "globals"; +// @ts-expect-error missing types +// import react from "eslint-plugin-react/configs/recommended.js"; +// import path from "pathe"; +// import { fileURLToPath } from "url"; +// import { FlatCompat } from "@eslint/eslintrc"; +// import js from "@eslint/js"; +// import tseslint from "typescript-eslint"; +// Mimic CommonJS variables to be used in ES module +// const esModuleFilename = fileURLToPath(import.meta.url); +// const esModuleDirname = path.dirname(esModuleFilename); +// @see https://eslint.org/docs/latest/use/configure/migration-guide#using-eslintrc-configs-in-flat-config +// const compat = new FlatCompat({ +// baseDirectory: esModuleDirname, +// recommendedConfig: js.configs.recommended, +// }); +// /* export default [ +// { +// // languageOptions: { +// // globals: { ...globals.browser, ...globals.node }, +// // parser: tseslint.parser, +// // parserOptions: { +// // project: ["./tsconfig.json"], +// // tsconfigRootDir: esModuleDirname, +// // }, +// // }, +// }, +// // { ignores: [".github", "build", "dist", "node_modules"] }, +// { +// languageOptions: { +// globals: { ...globals.browser, ...globals.node }, +// parser: tseslint.parser, +// parserOptions: { +// project: "./tsconfig.json", +// // project: ["./tsconfig.json"], +// // tsconfigRootDir: esModuleDirname, +// }, +// }, +// }, +// // ...compat.extends("xo-typescript"), +// // ...tseslint.configs.recommendedTypeChecked, +// // ...tseslint.configs.stylisticTypeChecked, +// // react, +// ]; */ +// const config = tseslint.config( +// { +// ignores: ["build/", "dist/", "node_modules/"], +// }, +// eslint.configs.recommended, +// ...tseslint.configs.recommendedTypeChecked, +// { +// files: ["**/*.test.ts"], +// rules: { +// "@typescript-eslint/require-await": "off", +// }, +// }, +// { +// languageOptions: { +// parserOptions: { +// parser: tseslint.parser, +// project: ["./tsconfig.json"], +// tsconfigRootDir: import.meta.dirname, +// }, +// }, +// plugins: {/**/}, +// rules: { +// "@typescript-eslint/no-unused-vars": [0, { argsIgnorePattern: "^_" }], +// }, +// }, +// ); +// export default config; + +// ///////////////////////////////////////////////////////////////////////////// + +// @type {import("eslint").Linter.Config} */ +// const config = { +// extends: [ +// "eslint:recommended", +// "next/core-web-vitals", +// "plugin:perfectionist/recommended-natural", +// ], +// ignorePatterns: ["**/node_modules/", "**/.next/"], +// parserOptions: { project: true }, +// plugins: ["@typescript-eslint", "drizzle", "perfectionist"], +// rules: { +// complexity: ["off", 10], +// "drizzle/enforce-delete-with-where": [ +// "off", +// { +// drizzleObjectName: ["db"], +// }, +// ], +// "drizzle/enforce-update-with-where": [ +// "off", +// { +// drizzleObjectName: ["db"], +// }, +// ], +// eqeqeq: ["off", "smart"], +// "import/extensions": "off", +// "import/namespace": "off", +// "import/no-duplicates": "off", +// "import/no-extraneous-dependencies": [ +// "off", +// { +// devDependencies: true, +// optionalDependencies: false, +// peerDependencies: false, +// }, +// ], +// "import/no-named-as-default": "off", +// "import/no-named-as-default-member": "off", +// "import/no-unresolved": "off", +// "import/prefer-default-export": "off", +// "max-depth": ["off", 3], +// "max-lines": ["off", 300], +// "no-restricted-imports": [ +// "off", +// { +// paths: [ +// { +// message: "Please use lodash/{module} import instead", +// name: "lodash", +// }, +// { +// message: "Please use aws-sdk/{module} import instead", +// name: "aws-sdk", +// }, +// { +// message: "Please use explicit import file", +// name: ".", +// }, +// ], +// }, +// ], +// "no-shadow": [ +// "off", +// { +// hoist: "all", +// }, +// ], +// "prefer-arrow/prefer-arrow-functions": [ +// "off", +// { +// classPropertiesAllowed: false, +// disallowPrototype: true, +// singleReturnOnly: false, +// }, +// ], +// "prefer-const": "off", +// "react/no-unescaped-entities": "off", +// "react-hooks/exhaustive-deps": "off", +// "react-hooks/rules-of-hooks": "off", +// }, +// }; +// module.exports = config; diff --git a/addons/cluster/temp/eslint.config.deprecated.b.txt b/addons/cluster/temp/eslint.config.deprecated.b.txt new file mode 100644 index 00000000..aa64c3ce --- /dev/null +++ b/addons/cluster/temp/eslint.config.deprecated.b.txt @@ -0,0 +1,2303 @@ +import tanstack from "@tanstack/eslint-plugin-query"; + +import { fileURLToPath, pathToFileURL } from "url"; + +import eslint from "@eslint/js"; +import reactCommunity from "@eslint-react/eslint-plugin"; +// @ts-expect-error - no types +import nextPlugin from "@next/eslint-plugin-next"; +import stylistic from "@stylistic/eslint-plugin"; +// @ts-expect-error - no types +import drizzle from "eslint-plugin-drizzle"; +// @ts-expect-error - no types +import eslintComments from "eslint-plugin-eslint-comments"; +import importX from "eslint-plugin-import-x"; +import jsonc from "eslint-plugin-jsonc"; +// @ts-expect-error - no types +import jsxA11yPlugin from "eslint-plugin-jsx-a11y"; +// @ts-expect-error - no types +import markdown from "eslint-plugin-markdown"; +import * as mdx from "eslint-plugin-mdx"; +import nodePlugin from "eslint-plugin-n"; +// @ts-expect-error - no types +import noComments from "eslint-plugin-no-comments"; +// @ts-expect-error - no types +import noRelative from "eslint-plugin-no-relative-import-paths"; +// @ts-expect-error - no types +import perfectionist from "eslint-plugin-perfectionist"; +// @ts-expect-error - no types +import promisePlugin from "eslint-plugin-promise"; +// @ts-expect-error - no types +import reactJsxRuntime from "eslint-plugin-react/configs/jsx-runtime.js"; +// @ts-expect-error - no types +import reactRecommended from "eslint-plugin-react/configs/recommended.js"; +// @ts-expect-error - no types +import reactHooks from "eslint-plugin-react-hooks"; +// @ts-expect-error - no types +import reactRefresh from "eslint-plugin-react-refresh"; +import tailwindReadable from "eslint-plugin-readable-tailwind"; +import * as regexp from "eslint-plugin-regexp"; +import security from "eslint-plugin-security"; +import * as sonar from "eslint-plugin-sonar"; +import sonarjs from "eslint-plugin-sonarjs"; +// @ts-expect-error - no types +import sort from "eslint-plugin-sort"; +// @ts-expect-error - no types +import sortExports from "eslint-plugin-sort-exports"; +// @ts-expect-error - no types // todo: tw v3 only +import tailwindcss from "eslint-plugin-tailwindcss"; +// @ts-expect-error - no types +import unicorn from "eslint-plugin-unicorn"; +import yaml from "eslint-plugin-yml"; +import globals from "globals"; +import tseslint from "typescript-eslint"; + +// TODO: Install and uncomment if you want to use the new React Compiler +// TODO: Set experimental.reactCompiler to true as well in next.config.js +// import compiler from "eslint-plugin-react-compiler"; +// @see https://eslint.org/docs/latest/use/configure/migration-guide +// fileURLToPath(new URL(file, import.meta.url)); +// @ts-expect-error - no types +// @ts-expect-error - no types +// @ts-expect-error - no types +// @ts-expect-error - no types +// @ts-expect-error - no types +// @ts-expect-error - no types +// @ts-expect-error - no types +// @ts-expect-error - no types +// @ts-expect-error - no types +// @ts-expect-error - no types +// @ts-expect-error - no types +// @ts-expect-error - no types +// @ts-expect-error - no types +// @ts-expect-error - no types +// @ts-expect-error - no types +// @ts-expect-error - no types // todo: tw v3 only +// @ts-expect-error - no types +const currentDirname = (/** @type {string} */ file) => + fileURLToPath(pathToFileURL(file)); + +// @see https://typescript-eslint.io/getting-started +export default tseslint.config( + eslint.configs.recommended, + ...markdown.configs.recommended, // @see https://eslint.org/docs/latest/rules + // @see https://typescript-eslint.io/rules + { + ignores: [ + "**/.git", + "**/.idea", + "**/.million", + "**/.next", + "**/.nyc_output", + "**/.turbo", + "**/.venv", + "**/.vercel", + "**/.yarn", + "**/*.gif", + "**/*.jpeg", + "**/*.lock", + "**/*.png", + "**/*.svg", + "**/build", + "**/coverage", + "**/dist-dev", + "**/dist", + "**/fixture", + "**/node_modules", + "**/package-lock.json", + "**/pnpm-lock.yaml", + "**/public", + "**/target", + "addons/.output", + ], + }, // @see https://eslint.style/rules + // @see https://eslint.org/docs/latest/use/configure + { + extends: [ + ...sonar.configs.flatRecommended, + sonarjs.configs.recommended, + regexp.configs["flat/recommended"], + security.configs.recommended, + stylistic.configs.customize({ + commaDangle: "never", + flat: true, + indent: 2, + jsx: true, + quotes: "double", + semi: true, + }), + ], // TODO: Config "typescript-eslint/base": Key "plugins": // TODO: Cannot redefine plugin "@typescript-eslint". + // import { recommended as putoutRecommended } from "eslint-plugin-putout/config"; + // ...putoutRecommended, + name: "@reliverse/config-eslint/core", + plugins: { + markdown: markdown, + "@stylistic": stylistic, + perfectionist: perfectionist, + }, // TODO: Trying to re-implement putout config + // import putout from "eslint-plugin-putout"; + // putout: putout, + rules: { + ...perfectionist.configs["recommended-natural"].rules, + + // @see https://eslint.style + "@stylistic/array-bracket-newline": [ + "off", + { + minItems: 10, + multiline: true, + }, + ], + "@stylistic/array-bracket-spacing": [ + "warn", + "never", + { + arraysInArrays: false, + objectsInArrays: false, + singleValue: false, + }, + ], + "@stylistic/arrow-parens": ["warn", "always"], + + // { requireForBlockBody: true }, + "@stylistic/arrow-spacing": [ + "warn", + { + after: true, + before: true, + }, + ], + "@stylistic/block-spacing": ["warn", "always"], + "@stylistic/brace-style": [ + "off", + "1tbs", + { + allowSingleLine: true, + }, + ], + "@stylistic/comma-dangle": [ + "warn", + { + // TODO: fix `Unexpected trailing comma.` when `type<>` + arrays: "always-multiline", + enums: "always-multiline", + exports: "always-multiline", + functions: "always-multiline", + generics: "always-multiline", + imports: "always-multiline", + objects: "always-multiline", + tuples: "always-multiline", + }, + ], + "@stylistic/comma-spacing": [ + "warn", + { + after: true, + before: false, + }, + ], + "@stylistic/comma-style": ["warn", "last"], + "@stylistic/computed-property-spacing": ["off", "always"], + "@stylistic/dot-location": ["warn", "property"], + "@stylistic/eol-last": "warn", + "@stylistic/function-call-argument-newline": ["warn", "consistent"], + "@stylistic/function-call-spacing": ["warn", "never"], + "@stylistic/function-paren-newline": [ + "off", // TODO: fix incompatibility with biome (or with something else) + "multiline-arguments", + ], + "@stylistic/generator-star-spacing": [ + "warn", + { + after: true, + before: false, + method: { + after: false, + before: true, + }, + }, + ], + "@stylistic/implicit-arrow-linebreak": ["off", "beside"], + "@stylistic/indent": [ + "off", // TODO: fix incompatibility with biome (or with something else) + 2, + { + SwitchCase: 1, + ignoredNodes: [ + "TSTypeLiteral > TSPropertySignature", + "TSUnionType > TSTypeLiteral", + ], + }, + ], + "@stylistic/indent-binary-ops": ["off", 2], + + // @see https://eslint.style/rules + "@stylistic/jsx-child-element-spacing": "off", + "@stylistic/jsx-closing-bracket-location": "off", + "@stylistic/jsx-closing-tag-location": "off", + "@stylistic/jsx-curly-newline": "off", + "@stylistic/jsx-indent": [ + "off", + 2, + { + checkAttributes: true, + indentLogicalExpressions: true, + }, + ], + "@stylistic/jsx-indent-props": ["warn", 2], + "@stylistic/jsx-one-expression-per-line": "off", + "@stylistic/jsx-pascal-case": "off", + "@stylistic/jsx-self-closing-comp": "off", + "@stylistic/jsx-wrap-multilines": [ + "warn", + { + arrow: "parens", + assignment: "parens", + condition: "ignore", + declaration: "parens", + logical: "ignore", + prop: "ignore", + return: "parens", + }, + ], + "@stylistic/key-spacing": [ + "warn", + { + afterColon: true, + beforeColon: false, + }, + ], + "@stylistic/keyword-spacing": [ + "warn", + { + after: true, + before: true, + }, + ], + "@stylistic/linebreak-style": ["warn", "unix"], + "@stylistic/lines-around-comment": [ + "warn", + { + afterBlockComment: false, + afterHashbangComment: true, + afterLineComment: false, + allowArrayEnd: true, + allowArrayStart: true, + allowBlockEnd: true, + allowBlockStart: true, + allowClassEnd: true, + allowClassStart: true, + allowEnumEnd: true, + allowEnumStart: true, + allowInterfaceEnd: true, + allowInterfaceStart: true, + allowModuleEnd: true, + allowModuleStart: true, + allowObjectEnd: true, + allowObjectStart: true, + allowTypeEnd: true, + allowTypeStart: true, + applyDefaultIgnorePatterns: true, + beforeBlockComment: true, + beforeLineComment: true, + ignorePattern: "@type\\s.+|@ts-expect-error", + }, + ], + + // @see https://eslint.style/rules/default/max-len + "@stylistic/max-len": [ + // @see https://github.com/eslint/eslint/issues/11325 + "warn", + { + // @see https://github.com/prettier/prettier/issues/12424 + code: 120, // TODO: change to 80 in 1.3.0 GA release + ignoreComments: false, + ignorePattern: "^[\\s]*(//|<!--) (es|style)lint-.+", + ignoreRegExpLiterals: false, + ignoreStrings: false, + ignoreTemplateLiterals: false, + ignoreTrailingComments: false, + ignoreUrls: true, + tabWidth: 2, + }, + ], + "@stylistic/max-statements-per-line": [ + "off", + { + max: 1, + }, + ], + "@stylistic/member-delimiter-style": [ + "warn", + { + multiline: { + delimiter: "comma", + requireLast: false, + }, + multilineDetection: "brackets", + overrides: { + interface: { + multiline: { + delimiter: "semi", + requireLast: true, + }, + singleline: { + delimiter: "semi", + requireLast: false, + }, + }, + typeLiteral: { + multiline: { + delimiter: "semi", + requireLast: true, + }, + singleline: { + delimiter: "semi", + requireLast: false, + }, + }, + }, + singleline: { + delimiter: "comma", + requireLast: false, + }, + }, + ], + "@stylistic/multiline-ternary": [ + "off", + "always-multiline", + { + ignoreJSX: true, + }, + ], + "@stylistic/new-parens": ["warn", "always"], + "@stylistic/newline-per-chained-call": [ + "warn", + { + ignoreChainWithDepth: 3, + }, + ], + "@stylistic/no-confusing-arrow": "off", + "@stylistic/no-extra-parens": "off", + "@stylistic/no-extra-semi": "warn", + "@stylistic/no-floating-decimal": "warn", + "@stylistic/no-mixed-operators": [ + "error", + { + allowSamePrecedence: true, + groups: [ + ["&", "|", "^", "~", "<<", ">>", ">>>"], + ["==", "!=", "===", "!==", ">", ">=", "<", "<="], + ["in", "instanceof"], + ], + }, + ], + "@stylistic/no-mixed-spaces-and-tabs": "error", + "@stylistic/no-multi-spaces": "warn", + "@stylistic/no-multiple-empty-lines": [ + "warn", + { + max: 1, + maxBOF: 0, + maxEOF: 0, + }, + ], + "@stylistic/no-tabs": "off", + "@stylistic/no-trailing-spaces": "off", + "@stylistic/no-whitespace-before-property": "warn", + "@stylistic/nonblock-statement-body-position": [ + "warn", + "beside", + { + overrides: { + do: "any", + else: "any", + if: "any", + while: "below", + }, + }, + ], + "@stylistic/object-curly-newline": [ + "warn", + { + consistent: true, + multiline: true, + }, + ], // { + // ExportDeclaration: { + // consistent: true, + // multiline: true, + // }, + // ImportDeclaration: "never", + // ObjectExpression: { + // consistent: true, + // multiline: true, + // }, + // ObjectPattern: { + // consistent: true, + // multiline: true, + // }, + // }, + "@stylistic/object-curly-spacing": [ + "warn", + "always", + { + arraysInObjects: true, + objectsInObjects: true, + }, + ], + "@stylistic/object-property-newline": [ + "warn", + { + allowAllPropertiesOnSameLine: true, + }, + ], + "@stylistic/one-var-declaration-per-line": "off", + "@stylistic/operator-linebreak": [ + "warn", + "after", + { + overrides: { + ":": "ignore", + "?": "ignore", + "||": "ignore", + }, + }, + ], + "@stylistic/padded-blocks": [ + "warn", + { + blocks: "never", + classes: "never", + switches: "never", + }, + { + allowSingleLineBlocks: false, + }, + ], + "@stylistic/padding-line-between-statements": [ + "warn", + { + blankLine: "always", + next: "function", + prev: "function", + }, + { + blankLine: "always", + next: "*", + prev: ["const", "let", "var"], + }, + { + blankLine: "any", + next: ["const", "let", "var"], + prev: ["const", "let", "var"], + }, + { + blankLine: "always", + next: ["multiline-const", "multiline-let", "multiline-var"], + prev: ["multiline-const", "multiline-let", "multiline-var"], + }, + { + blankLine: "always", + next: ["throw", "return"], + prev: "*", + }, + { + blankLine: "always", + next: "const", + prev: ["interface", "type"], + }, + { + blankLine: "always", + next: ["if", "for", "class", "switch", "while", "with"], + prev: "*", + }, + { + blankLine: "always", + next: "*", + prev: ["if", "for", "class", "switch", "while", "with"], + }, + { + blankLine: "always", + next: ["interface", "type"], + prev: "*", + }, + { + blankLine: "always", + next: "function", + prev: "function-overload", + }, + { + blankLine: "always", + next: "export", + prev: "*", + }, + ], + "@stylistic/quote-props": ["warn", "as-needed"], + "@stylistic/quotes": [ + "warn", + "double", + { + avoidEscape: true, + }, + ], + "@stylistic/rest-spread-spacing": ["warn", "never"], + "@stylistic/semi": ["warn", "always"], + "@stylistic/semi-spacing": [ + "off", + { + after: true, + before: false, + }, + ], + "@stylistic/semi-style": ["warn", "last"], + "@stylistic/space-before-blocks": ["warn", "always"], + "@stylistic/space-before-function-paren": [ + "warn", + { + anonymous: "always", + named: "never", + }, + ], + "@stylistic/space-in-parens": ["warn", "never"], + "@stylistic/space-infix-ops": [ + "warn", + { + int32Hint: false, + }, + ], + "@stylistic/space-unary-ops": [ + "warn", + { + nonwords: false, + overrides: { + "++": false, + new: false, + }, + words: true, + }, + ], + "@stylistic/spaced-comment": [ + "warn", + "always", + { + block: { + balanced: true, + exceptions: ["*"], + markers: ["!"], + }, + line: { + exceptions: ["-", "+"], + markers: ["/"], + }, + }, + ], + "@stylistic/switch-colon-spacing": [ + "warn", + { + after: true, + before: false, + }, + ], + "@stylistic/template-curly-spacing": ["warn", "never"], + "@stylistic/template-tag-spacing": ["warn", "never"], + "@stylistic/type-annotation-spacing": [ + "warn", + { + after: true, + before: false, + overrides: { + arrow: { + after: true, + before: true, + }, + }, + }, + ], + "@stylistic/type-generic-spacing": "off", + "@stylistic/wrap-iife": ["warn", "outside"], + "@stylistic/wrap-regex": "off", + "@stylistic/yield-star-spacing": ["warn", "both"], + + // @see https://perfectionist.dev + "perfectionist/sort-exports": [ + "warn", + { + // we're using perfectionist with eslint-plugin-sort + order: "asc", + type: "natural", + }, + ], + "perfectionist/sort-imports": [ + "warn", + { + order: "asc", + type: "natural", + "custom-groups": { + type: { + react: "react", + }, + value: { + react: ["react", "react-*"], + storybook: ["@storybook/**"], + tanstack: ["@tanstack/**", "@trpc/**"], + }, + }, + groups: [ + [ + "type", + "builtin-type", + "customTypes", + "internal-type", + "parent-type", + "sibling-type", + "index-type", + ], + "react", + "tanstack", + "trpc", + "siblings", + "builtin", + "external", + "internal", + ["parent", "sibling", "index"], + "side-effect", + "object", + "style", + "unknown", + ], + "newlines-between": "always", + }, + ], + "perfectionist/sort-object-types": [ + "warn", + { + // we just keep it in sync with + // perfectionist/sort-objects + order: "asc", + type: "natural", + "custom-groups": { + id: ["_", "id", "key"], + type: ["order", "type", "kind"], + meta: ["name", "meta", "slug", "title", "description"], + xs: ["xs", "-xs", "tighter"], + sm: ["sm", "-sm", "tight"], + md: ["md", "-md", "normal"], + lg: ["lg", "-lg", "wide"], + Nxl: ["*xl", "widest"], + xl: ["xl", "-xl", "wider", "loose"], + bottom: ["createdAt", "updatedAt"], + db: ["userId", "productId", "storeId", "createdById"], + eslint: ["files", "extends"], + onoff: ["on", "off"], + processors: [ + "css", + "filesystem", + "html", + "ignore", + "ignore", + "javascript", + "json", + "markdown", + "typescript", + "typos", + "wasm", + "yaml", + ], + }, + groups: [ + "id", + "db", + "eslint", + "type", + "meta", + "xs", + "sm", + "md", + "lg", + "xl", + "Nxl", + "processors", + "onoff", + "unknown", + "bottom", + ], + }, + ], + "perfectionist/sort-objects": [ + "warn", + { + // we just keep it in sync with + // perfectionist/sort-object-types + order: "asc", + type: "natural", + "custom-groups": { + id: ["_", "id", "key"], + type: ["order", "type", "kind"], + meta: ["name", "meta", "slug", "title", "description"], + xs: ["xs", "-xs", "tighter"], + sm: ["sm", "-sm", "tight"], + md: ["md", "-md", "normal"], + lg: ["lg", "-lg", "wide"], + Nxl: ["*xl", "widest"], + xl: ["xl", "-xl", "wider", "loose"], + bottom: ["createdAt", "updatedAt"], + db: ["userId", "productId", "storeId", "createdById"], + eslint: ["files", "extends"], + onoff: ["on", "off"], + processors: [ + "css", + "filesystem", + "html", + "ignore", + "ignore", + "javascript", + "json", + "markdown", + "typescript", + "typos", + "wasm", + "yaml", + ], + }, + groups: [ + "id", + "db", + "eslint", + "type", + "meta", + "xs", + "sm", + "md", + "lg", + "xl", + "Nxl", + "processors", + "onoff", + "unknown", + "bottom", + ], + }, + ], + "perfectionist/sort-union-types": [ + "warn", + { + order: "asc", + type: "natural", + }, + ], + }, + }, // @see https://github.com/coderaiser/putout/tree/master/packages/eslint-plugin-putout + { + extends: [ + ...tseslint.configs.recommendedTypeChecked, + ...tseslint.configs.stylisticTypeChecked, + nodePlugin.configs["flat/recommended-module"], + reactCommunity.configs["recommended-type-checked"], + sort.configs["flat/recommended"], + unicorn.configs["flat/recommended"], + ], + files: ["**/*.{ts,tsx,js,jsx}"], + name: "@reliverse/config-eslint/react", + languageOptions: { + globals: { + ...globals.browser, + ...globals.node, + }, + parser: tseslint.parser, + parserOptions: { + ecmaFeatures: { + impliedStrict: true, + jsx: true, + }, + ecmaVersion: "latest", + env: { + browser: true, + es2024: true, + node: true, + }, + projectService: true, + sourceType: "module", + tsconfigRootDir: currentDirname("./"), + warnOnUnsupportedTypeScriptVersion: false, + }, + }, + plugins: { + ...reactRecommended.plugins, + "@next/next": nextPlugin, + "@tanstack/query": tanstack, + "@typescript-eslint": tseslint.plugin, + drizzle: drizzle, + "eslint-comments": eslintComments, + "import-x": importX, + "jsx-a11y": { + rules: jsxA11yPlugin.rules, + }, + "no-relative-import-paths": noRelative, + promise: promisePlugin, + "react/jsx-runtime": reactJsxRuntime, + "react-hooks": reactHooks, + "react-refresh": reactRefresh, + "readable-tailwind": tailwindReadable, + sort: sort, + "sort-exports": sortExports, + tailwindcss: tailwindcss, + }, // TODO: npx nypm add -D babel-plugin-react-compiler + // "react-compiler": compiler, + // TODO: consider using eslint disable comments instead of completely disabling some of the rules + // TODO: @see https://eslint.org/docs/latest/use/configure/rules#using-configuration-comments-1 + rules: { + ...importX.configs.recommended.rules, + ...jsxA11yPlugin.configs.recommended.rules, + ...nextPlugin.configs.recommended.rules, + ...promisePlugin.configs.recommended.rules, + ...reactHooks.configs.recommended.rules, + ...reactJsxRuntime.rules, + ...reactRecommended.rules, + ...tailwindReadable.configs.error.rules, + ...tailwindReadable.configs.warning.rules, + ...tailwindcss.configs.recommended.rules, + + // @see https://eslint-react.xyz/rules/overview + "@eslint-react/no-array-index-key": "off", + "@eslint-react/no-leaked-conditional-rendering": "off", + "@eslint-react/no-unstable-context-value": "off", + "@eslint-react/no-unstable-default-props": "off", + "@eslint-react/prefer-destructuring-assignment": "off", + "@eslint-react/prefer-read-only-props": "off", + + // @see https://nextjs.org/docs/app/building-your-application/configuring/eslint#eslint-plugin + "@next/next/no-duplicate-head": "off", + "@next/next/no-html-link-for-pages": "off", + + // @see https://tanstack.com/query/latest/docs/eslint/eslint-plugin-query + "@tanstack/query/exhaustive-deps": "error", + "@tanstack/query/no-rest-destructuring": "error", + "@tanstack/query/stable-query-client": "error", + + // @see https://typescript-eslint.io/rules + "@typescript-eslint/array-type": [ + "off", + { + // default: "array-simple", + default: "generic", + }, + ], + "@typescript-eslint/await-thenable": "off", + "@typescript-eslint/ban-ts-comment": [ + "error", + { + minimumDescriptionLength: 9, + "ts-check": false, + "ts-expect-error": "allow-with-description", + "ts-ignore": true, + "ts-nocheck": true, + }, + ], + "@typescript-eslint/camelcase": "off", + "@typescript-eslint/consistent-indexed-object-style": "error", + "@typescript-eslint/consistent-type-assertions": [ + "error", + { + assertionStyle: "as", + }, + ], + "@typescript-eslint/consistent-type-definitions": ["error", "type"], + "@typescript-eslint/consistent-type-imports": [ + "warn", + { + disallowTypeAnnotations: true, + fixStyle: "separate-type-imports", + prefer: "type-imports", + }, + ], + "@typescript-eslint/default-param-last": "off", + "@typescript-eslint/dot-notation": "off", + "@typescript-eslint/explicit-function-return-type": [ + "off", + { + allowExpressions: true, + allowTypedFunctionExpressions: true, + }, + ], + "@typescript-eslint/explicit-member-accessibility": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/method-signature-style": ["off", "method"], + "@typescript-eslint/naming-convention": [ + "error", + { + custom: { + match: true, + regex: "^I[A-Z]", + }, + format: ["PascalCase"], + selector: "interface", + }, + { + format: ["PascalCase"], + selector: "typeLike", + }, + ], + "@typescript-eslint/no-array-constructor": "off", + "@typescript-eslint/no-base-to-string": "off", + "@typescript-eslint/no-confusing-non-null-assertion": "off", + "@typescript-eslint/no-duplicate-enum-values": "off", + "@typescript-eslint/no-duplicate-type-constituents": "off", + "@typescript-eslint/no-dynamic-delete": "off", + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-floating-promises": "off", + "@typescript-eslint/no-for-in-array": "off", + "@typescript-eslint/no-implied-eval": "off", + "@typescript-eslint/no-import-type-side-effects": "off", + "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/no-invalid-void-type": "off", + "@typescript-eslint/no-misused-new": "off", + "@typescript-eslint/no-misused-promises": [ + "off", + { + checksVoidReturn: { + attributes: false, + }, + }, + ], + "@typescript-eslint/no-namespace": "error", + "@typescript-eslint/no-non-null-asserted-nullish-coalescing": "off", + "@typescript-eslint/no-non-null-asserted-optional-chain": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-redeclare": "off", + "@typescript-eslint/no-redundant-type-constituents": "off", + "@typescript-eslint/no-require-imports": "off", + "@typescript-eslint/no-shadow": "off", + "@typescript-eslint/no-this-alias": "off", + "@typescript-eslint/no-unnecessary-boolean-literal-compare": "off", + "@typescript-eslint/no-unnecessary-condition": [ + "off", + { + allowConstantLoopConditions: false, + }, + ], + "@typescript-eslint/no-unnecessary-qualifier": "off", + "@typescript-eslint/no-unnecessary-type-arguments": "off", + "@typescript-eslint/no-unnecessary-type-assertion": "off", + "@typescript-eslint/no-unnecessary-type-constraint": "off", + "@typescript-eslint/no-unsafe-argument": "off", + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-unsafe-call": "off", + "@typescript-eslint/no-unsafe-declaration-merging": "off", + "@typescript-eslint/no-unsafe-enum-comparison": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unsafe-return": "off", + "@typescript-eslint/no-unused-expressions": [ + "off", + { + allowShortCircuit: true, + }, + ], + "@typescript-eslint/no-unused-vars": [ + "off", + { + args: "all", + argsIgnorePattern: "^_", + caughtErrors: "all", + caughtErrorsIgnorePattern: "^_", + destructuredArrayIgnorePattern: "^_", + ignoreRestSiblings: true, + varsIgnorePattern: "^_", + }, + ], + "@typescript-eslint/no-use-before-define": "off", + "@typescript-eslint/non-nullable-type-assertion-style": "off", + "@typescript-eslint/only-throw-error": "off", + "@typescript-eslint/prefer-for-of": "off", + "@typescript-eslint/prefer-includes": "off", + "@typescript-eslint/prefer-literal-enum-member": "off", + "@typescript-eslint/prefer-nullish-coalescing": [ + "off", + { + ignoreConditionalTests: true, + ignorePrimitives: true, + }, + ], + "@typescript-eslint/prefer-optional-chain": "off", + "@typescript-eslint/prefer-readonly": "off", + "@typescript-eslint/prefer-reduce-type-parameter": "off", + "@typescript-eslint/prefer-regexp-exec": "off", + "@typescript-eslint/prefer-string-starts-ends-with": "off", + "@typescript-eslint/promise-function-async": "off", + "@typescript-eslint/require-array-sort-compare": "off", + "@typescript-eslint/require-await": "off", + "@typescript-eslint/restrict-plus-operands": "off", + "@typescript-eslint/restrict-template-expressions": [ + "off", + { + allowAny: true, + allowBoolean: true, + allowNullish: true, + allowNumber: true, + allowRegExp: true, + }, + ], + "@typescript-eslint/return-await": "off", + "@typescript-eslint/strict-boolean-expressions": "off", + "@typescript-eslint/switch-exhaustiveness-check": "off", + + "@typescript-eslint/triple-slash-reference": [ + "error", + { + lib: "always", + path: "always", + types: "prefer-import", + }, + ], + + // We don't use classes (!) This rule adds about 23s to the lint time + "@typescript-eslint/unbound-method": "off", + "@typescript-eslint/unified-signatures": "off", + + // @see https://eslint.org/docs/latest/rules + complexity: [ + "warn", + { + max: 20, + }, + ], + "consistent-function-scoping": "off", + "constructor-super": "off", + curly: ["warn", "all"], + + // @see https://orm.drizzle.team/docs/eslint-plugin + "drizzle/enforce-delete-with-where": [ + "warn", + { + drizzleObjectName: ["db", "ctx.db"], + }, + ], + "drizzle/enforce-update-with-where": [ + "warn", + { + drizzleObjectName: ["db", "ctx.db"], + }, + ], + + // @see https://eslint.org/docs/latest/rules + "error-message": "off", + + // @see https://mysticatea.github.io/eslint-plugin-eslint-comments/rules + "eslint-comments/disable-enable-pair": [ + "off", + { + allowWholeFile: false, + }, + ], + "eslint-comments/no-aggregating-enable": "error", + "eslint-comments/no-duplicate-disable": "error", + "eslint-comments/no-unlimited-disable": "error", + "eslint-comments/no-unused-disable": "error", + "eslint-comments/no-unused-enable": "error", + "eslint-comments/no-use": [ + "error", + { + allow: [ + "eslint-disable", + "eslint-disable-line", + "eslint-disable-next-line", + "eslint-enable", + "global", + ], + }, + ], + "eslint-comments/require-description": [ + "off", + { + ignore: [], + }, + ], + + // @see https://eslint.org/docs/latest/rules + "filename-case": "off", + "for-direction": "off", + "getter-return": "off", + "id-denylist": [ + "off", + "any", + "Boolean", + "callback", + "cb", + "data", + "e", + "err", + "number", + "Number", + "string", + "String", + "undefined", + "Undefined", + ], + "id-match": "error", + "import-style": "off", + + // @see https://github.com/un-ts/eslint-plugin-import-x + "import-x/consistent-type-specifier-style": ["error", "prefer-top-level"], + "import-x/default": "off", + "import-x/export": "error", + "import-x/first": "off", + "import-x/named": "off", + "import-x/namespace": "off", + "import-x/newline-after-import": "error", + "import-x/no-absolute-path": "error", + "import-x/no-amd": "error", + "import-x/no-anonymous-default-export": "off", + "import-x/no-cycle": [ + "off", + { + ignoreExternal: true, + maxDepth: 3, + }, + ], + "import-x/no-default-export": "off", + "import-x/no-extraneous-dependencies": [ + "off", + { + devDependencies: true, + optionalDependencies: false, + peerDependencies: true, + }, + ], + "import-x/no-mutable-exports": "error", + "import-x/no-named-as-default": "off", + "import-x/no-named-as-default-member": "off", + "import-x/no-named-default": "error", + "import-x/no-named-export": "off", + "import-x/no-relative-packages": "off", + "import-x/no-self-import": "off", + "import-x/no-unresolved": "off", + "import-x/no-useless-path-segments": [ + "error", + { + commonjs: true, + }, + ], + "import-x/prefer-default-export": "off", + + // @see https://github.com/jsx-eslint/eslint-plugin-jsx-a11y + "jsx-a11y/alt-text": [ + "error", + { + area: ["Area"], + elements: ["img", "object", "area", 'input[type="image"]'], + img: ["Image"], + 'input[type="image"]': ["InputImage"], + object: ["Object"], + }, + ], + "jsx-a11y/aria-props": "error", + "jsx-a11y/aria-proptypes": "error", + "jsx-a11y/aria-unsupported-elements": "error", + "jsx-a11y/click-events-have-key-events": "off", + "jsx-a11y/no-noninteractive-element-interactions": "off", + "jsx-a11y/role-has-required-aria-props": "error", + "jsx-a11y/role-supports-aria-props": "error", + "max-depth": ["warn", 10], + "max-lines": ["warn", 700], + "max-lines-per-function": [ + "warn", + { + IIFEs: true, + max: 200, + skipBlankLines: true, + skipComments: true, + }, + ], + "max-statements": [ + "warn", + 100, + { + ignoreTopLevelFunctions: false, + }, + ], + + // @see https://github.com/eslint-community/eslint-plugin-n#-rules + "n/exports-style": ["off", "module.exports"], + "n/no-extraneous-import": "off", + "n/no-missing-import": "off", + "n/no-process-exit": "off", + "n/no-unpublished-import": "off", + "n/no-unsupported-features/es-syntax": "off", + "n/no-unsupported-features/node-builtins": "off", + + // @see https://eslint.org/docs/latest/rules + "no-abusive-eslint-disable": "off", + "no-anonymous-default-export": "off", + "no-array-reduce": "off", + "no-async-promise-executor": "off", + "no-await-in-promise-methods": "off", + "no-case-declarations": "off", + "no-compare-neg-zero": "off", + "no-cond-assign": "off", + "no-console": [ + "error", + { + allow: ["warn", "error", "info", "trace", "clear"], + }, + ], + "no-const-assign": "off", + "no-constant-binary-expression": "off", + "no-constant-condition": "off", + "no-control-regex": "off", + "no-delete-var": "off", + "no-document-cookie": "off", + "no-dupe-args": "off", + "no-dupe-else-if": "off", + "no-dupe-keys": "off", + "no-duplicate-case": "off", + "no-empty": "off", + "no-empty-file": "off", + "no-empty-pattern": "off", + "no-empty-static-block": "off", + "no-ex-assign": "off", + "no-fallthrough": [ + "error", + { + commentPattern: ".*intentional fallthrough.*", + }, + ], + "no-func-assign": "off", + "no-global-assign": "off", + "no-import-assign": "off", + "no-invalid-regexp": "off", + "no-invalid-remove-event-listener": "off", + "no-irregular-whitespace": "off", + "no-keyword-prefix": "off", + "no-lonely-if": "error", + "no-magic-numbers": [ + "off", + { + ignore: [-1, 0, 1], + detectObjects: true, + enforceConst: true, + ignoreArrayIndexes: true, + }, + ], + "no-new-native-nonconstructor": "off", + "no-nonoctal-decimal-escape": "off", + "no-obj-calls": "off", + "no-object-as-default-parameter": "off", + "no-octal": "off", + "no-prototype-builtins": "off", + "no-redeclare": "off", + + // @see https://github.com/MelvinVermeer/eslint-plugin-no-relative-import-paths + "no-relative-import-paths/no-relative-import-paths": [ + "error", + { + allowSameFolder: false, + prefix: "~", + rootDir: "src", + }, + ], + + // @see https://eslint.org/docs/latest/rules + "no-restricted-exports": [ + "error", + { + restrictedNamedExports: ["default", "then"], + }, + ], + "no-restricted-globals": [ + "error", + { + name: "isFinite", + message: + "Use Number.isFinite instead https://github.com/airbnb/javascript#standard-library--isfinite", + }, + { + name: "fetch", + message: "Please use https://github.com/unjs/ofetch instead.", + }, + { + name: "crypto", + message: + "Please use uncrypto instead (https://unjs.io/packages/uncrypto)", + }, + { + name: "isNaN", + message: + "Use Number.isNaN instead https://github.com/airbnb/javascript#standard-library--isnan", + }, + ], + "no-shadow-restricted-names": "off", + "no-sparse-arrays": "off", + "no-thenable": "off", + "no-this-assignment": "off", + "no-this-before-super": "off", + "no-throw-literal": "off", + "no-undef": "off", + "no-unexpected-multiline": "off", + "no-unnecessary-polyfills": "off", + "no-unreachable": "off", + "no-unsafe-finally": "off", + "no-unsafe-negation": "off", + "no-unsafe-optional-chaining": [ + "error", + { + disallowArithmeticOperators: true, + }, + ], + "no-unused-expressions": "off", + "no-unused-properties": "off", + "no-unused-vars": "off", + "no-use-before-define": [ + "error", + { + allowNamedExports: false, + classes: false, + functions: false, + variables: false, + }, + ], + "no-useless-backreference": "off", + "no-useless-catch": "off", + "no-useless-escape": "off", + "no-useless-rename": [ + "off", + { + ignoreDestructuring: false, + ignoreExport: false, + ignoreImport: false, + }, + ], + "no-useless-switch-case": "off", + "no-warning-comments": [ + "off", + { + decoration: ["/", "*", "=", "!", "#"], + + // `location: "start"` helper + location: "anywhere", // TODO: consider using "start" option instead + // TODO: consider adding: "todo", "hack", "hacky", "temp", "temporary" + terms: ["fixme", "xxx"], + }, + ], + "no-with": "off", + "object-shorthand": "off", + "prefer-blob-reading-methods": "off", + "prefer-const": [ + "error", + { + destructuring: "any", + ignoreReadBeforeAssign: true, + }, + ], + "prefer-destructuring": [ + "off", + { + AssignmentExpression: { + array: true, + object: false, + }, + VariableDeclarator: { + array: false, + object: true, + }, + }, + { + enforceForRenamedProperties: false, + }, + ], + "prefer-dom-node-text-content": "off", + "prefer-event-target": "off", + "prefer-logical-operator-over-ternary": "off", + "prefer-template": "error", + "prefer-top-level-await": "off", + "promise/always-return": "off", + "promise/catch-or-return": "off", + "react/jsx-no-target-blank": [ + "error", + { + allowReferrer: true, + enforceDynamicLinks: "always", + forms: true, + links: true, + warnOnSpreadAttributes: false, + }, + ], + + // @see https://github.com/jsx-eslint/eslint-plugin-react + "react/no-invalid-html-attribute": "error", + "react/no-unescaped-entities": "off", + "react/no-unknown-property": "off", + + // TODO: https://github.com/shadcn-ui/ui/issues/120 + "react/prop-types": "off", + + // "react-compiler/react-compiler": "error", + "react/react-in-jsx-scope": "off", + + // TODO: fix error in eslint console + "react-hooks/exhaustive-deps": "off", // @see https://npmjs.com/package/eslint-plugin-react-compiler + // @see https://npmjs.com/package/eslint-plugin-react-hooks + "react-hooks/rules-of-hooks": "off", + + // @see https://github.com/ArnaudBarre/eslint-plugin-react-refresh + "react-refresh/only-export-components": "off", + + // @see https://github.com/schoero/eslint-plugin-readable-tailwind + "readable-tailwind/multiline": ["off"], // { + // callees: [ + // ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], + // ["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"], + // ], + // group: "newLine", + // printWidth: 60, + // }, + "readable-tailwind/no-unnecessary-whitespace": ["error"], // { + // callees: [ + // ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], + // ["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"], + // ], + // }, + "readable-tailwind/sort-classes": ["error"], // { + // callees: [ + // ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], + // ["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"], + // ], + // }, + // @see https://github.com/eslint-community/eslint-plugin-security#rules + "security/detect-non-literal-fs-filename": "off", + "security/detect-object-injection": "off", + "security/detect-unsafe-regex": "off", + + // @see https://github.com/un-ts/eslint-plugin-sonar#rule-list + "sonar/deprecation": "off", + "sonar/max-union-size": "off", + "sonar/no-alphabetical-sort": "off", + "sonar/no-dead-store": "off", + "sonar/no-redundant-optional": "off", + "sonar/no-try-promise": "off", + "sonar/no-useless-intersection": "off", + "sonar/unused-import": "off", + + // @see https://github.com/SonarSource/eslint-plugin-sonarjs#rules + "sonarjs/no-all-duplicated-branches": "off", + "sonarjs/no-duplicate-string": "off", + "sonarjs/no-gratuitous-expressions": "off", + "sonarjs/no-identical-expressions": "off", + "sonarjs/no-ignored-return": "off", + "sonarjs/no-nested-template-literals": "off", + "sonarjs/no-small-switch": "off", + "sonarjs/no-unused-collection": "off", + + // @see https://github.com/mskelton/eslint-plugin-sort#list-of-supported-rules + "sort/destructuring-properties": "off", + "sort/export-members": "off", + "sort/exports": [ + "off", + { + caseSensitive: false, + groups: [ + { + order: 50, + type: "default", + }, + { + order: 10, + type: "sourceless", + }, + { + order: 30, + regex: "^~", + }, + { + order: 20, + type: "dependency", + }, + { + order: 40, + type: "other", + }, + ], + natural: true, + typeOrder: "first", + }, + ], + "sort/import-members": "off", + "sort/imports": "off", + "sort/object-properties": "off", + "sort/string-enums": "error", + "sort/string-unions": "error", + "sort/type-properties": "off", // @see https://github.com/francoismassart/eslint-plugin-tailwindcss#supported-rules + // @see https://github.com/jrdrg/eslint-plugin-sort-exports#usage + "sort-exports/sort-exports": [ + "off", + { + disableAutofixer: true, + ignoreCase: true, + sortDir: "asc", + sortExportKindFirst: "type", + }, + ], + + // We're using readable-tailwind plugin + "tailwindcss/classnames-order": "off", + + // Adds ~15s to the lint time (!) + "tailwindcss/no-custom-classname": "off", + + // @see https://github.com/sindresorhus/eslint-plugin-unicorn + "unicorn/catch-error-name": [ + "off", + { + name: "e", + }, + ], + "unicorn/consistent-destructuring": "off", + "unicorn/consistent-function-scoping": "off", + "unicorn/empty-brace-spaces": "off", + "unicorn/filename-case": [ + "off", + { + ignore: [ + ".*\\.(jsx|tsx)$", + + // TODO: remove in GA branch of 1.3.0 version + ".*\\.(js|ts|cjs|cts|mjs|mts|d\\.ts)$", + + // TODO: decide in 1.3.x + "^(layout|page|loading|not-found|error|global-error)\\.(jsx|tsx)$", + "^(template|default|icon|apple-icon|opengraph-image)\\.(jsx|tsx)$", + ], + case: "pascalCase", + }, + ], // TODO: 1.3.x: + // - Use PascalCase for all React component names. + // - Use camelCase for functions and variable names. + // - Avoid abbreviations in component names. + // - Name files using PascalCase to match the component name. + // - Document any exceptions or additional rules. + "unicorn/import-style": "off", + "unicorn/no-abusive-eslint-disable": "off", + "unicorn/no-anonymous-default-export": "off", + "unicorn/no-array-callback-reference": "off", + "unicorn/no-array-reduce": "off", + "unicorn/no-await-expression-member": "off", + "unicorn/no-empty-file": "off", + "unicorn/no-negated-condition": "off", + "unicorn/no-nested-ternary": "off", + "unicorn/no-null": [ + "off", + { + checkStrictEquality: true, + }, + ], + "unicorn/no-object-as-default-parameter": "off", + "unicorn/no-unused-properties": "warn", + "unicorn/no-useless-switch-case": "off", + "unicorn/numeric-separators-style": [ + "off", + { + onlyIfContainsSeparator: true, + }, + ], + "unicorn/prefer-array-flat-map": "off", + "unicorn/prefer-code-point": "off", + "unicorn/prefer-date-now": "off", + "unicorn/prefer-logical-operator-over-ternary": "off", + "unicorn/prefer-native-coercion-functions": "off", + "unicorn/prefer-node-protocol": "off", + "unicorn/prefer-optional-catch-binding": "off", + "unicorn/prefer-string-raw": "off", + "unicorn/prefer-string-replace-all": "off", + "unicorn/prefer-string-slice": "off", + "unicorn/prefer-switch": "off", + "unicorn/prefer-ternary": "off", + "unicorn/prefer-top-level-await": "off", + "unicorn/prevent-abbreviations": [ + "warn", + { + // @see https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prevent-abbreviations.md + ignore: ["\\.e2e$", /^ignore/i], + + // note: if rule script realizes that it cannot rename specific word elsewhere, then auto-fix is turned off + allowList: { + getInitialProps: true, + }, + extendDefaultReplacements: true, + replacements: { + // TODO: consider limiting to a single option, in this case auto-fix works; check the default replacements: + // TODO: @see https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/rules/shared/abbreviations.js + db: { + database: false, + }, + dev: { + development: false, + }, + dir: { + direction: false, + directory: true, + }, + dst: { + daylightSavingTime: false, + destination: true, + distribution: false, + }, + e: { + error: false, + event: true, + }, + env: { + environment: false, + }, + mod: { + module: false, + }, + props: { + properties: false, + }, + ref: { + reference: false, + }, + res: { + resource: false, + response: false, + result: false, + }, + }, + }, + ], + "unicorn/switch-case-braces": ["off", "avoid"], + "use-isnan": "off", + "valid-typeof": "off", + yoda: [ + "error", + "never", + { + onlyEquality: true, + }, + ], + }, + settings: { + // @see https://github.com/jsx-eslint/eslint-plugin-react#configuration + formComponents: ["Form"], + + // @see https://github.com/un-ts/eslint-plugin-import-x + "import-x/internal-regex": "^~/", + + // @see https://github.com/jsx-eslint/eslint-plugin-react#configuration + linkComponents: [ + { + name: "Link", + linkAttribute: ["href"], + }, + ], + + // @see https://github.com/jsx-eslint/eslint-plugin-react#configuration + react: { + version: "detect", + }, + + // @see https://eslint-react.xyz/docs/configuration + reactOptions: { + additionalHooks: { + useLayoutEffect: ["useIsomorphicLayoutEffect"], + }, + importSource: "react", + jsxPragma: "createElement", + jsxPragmaFrag: "Fragment", + version: "detect", + }, + + // @see https://tailwindcss.com + tailwindcss: { + callees: ["cn", "classnames", "buttonClassName"], + }, + }, + }, + { + // @see https://typescript-eslint.io/getting-started/typed-linting#how-can-i-disable-type-aware-linting-for-a-subset-of-files + extends: [tseslint.configs.disableTypeChecked], + files: ["**/*.{js,jsx}"], + name: "@reliverse/config-eslint/js", + rules: { + // turn off rules that don't apply to javascript + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/internal/no-poorly-typed-ts-props": "off", + }, + }, // @see https://github.com/eslint/eslint-plugin-markdown#advanced-configuration + { + files: ["**/*.md"], + name: "@reliverse/config-eslint/markdown-core", + processor: "markdown/markdown", + }, + { + files: ["**/*.md/*.js"], + name: "@reliverse/config-eslint/markdown-codeblock", + }, // @see https://github.com/mdx-js/eslint-mdx#flat-config + { + ...mdx.flat, + files: ["**/*.mdx"], + name: "@reliverse/config-eslint/mdx-core", + processor: mdx.createRemarkProcessor({ + languageMapper: {}, + lintCodeBlocks: true, + }), + rules: { + ...mdx.flat.rules, + "@stylistic/max-len": [ + "warn", + { + code: 1000, + }, + ], + "@stylistic/semi": "off", + "mdx/remark": "error", + "no-unused-expressions": "off", + }, + }, + { + ...mdx.flatCodeBlocks, + files: ["**/*.mdx/*.js"], + name: "@reliverse/config-eslint/mdx-codeblock", + rules: { ...mdx.flatCodeBlocks.rules }, + }, // @see https://ota-meshi.github.io/eslint-plugin-yml + { + extends: [...yaml.configs["flat/standard"]], + files: ["**/*.{yml,yaml}"], + name: "@reliverse/config-eslint/yaml", + plugins: { + yml: yaml, + }, + rules: { + "@stylistic/max-len": "off", + "@stylistic/spaced-comment": "off", + "yml/no-empty-mapping-value": "off", + }, + }, // @see https://ota-meshi.github.io/eslint-plugin-jsonc/rules + { + extends: [...jsonc.configs["flat/recommended-with-json"]], + files: ["**/*.{json,jsonc,json5}"], + name: "@reliverse/config-eslint/jsonc-core", + plugins: { + jsonc: jsonc, + }, + rules: { + // TODO: implement no-dupe-values rule (https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-dupe-keys.html) + "@stylistic/max-len": "off", + "jsonc/array-bracket-newline": [ + "off", + { + minItems: 3, + multiline: true, + }, + ], + "jsonc/array-bracket-spacing": ["off", "always"], + "jsonc/array-element-newline": ["off", "always"], + "jsonc/comma-dangle": ["error", "never"], + "jsonc/comma-style": ["error", "last"], + "jsonc/indent": ["error", 2], + "jsonc/key-spacing": [ + "error", + { + afterColon: true, + beforeColon: false, + mode: "strict", + }, + ], + "jsonc/no-bigint-literals": "error", + "jsonc/no-binary-expression": "error", + "jsonc/no-binary-numeric-literals": "error", + "jsonc/no-dupe-keys": ["error"], + "jsonc/no-escape-sequence-in-identifier": "error", + "jsonc/no-floating-decimal": "error", + "jsonc/no-hexadecimal-numeric-literals": "error", + "jsonc/no-infinity": "error", + "jsonc/no-irregular-whitespace": [ + "error", + { + skipComments: false, + skipRegExps: false, + skipStrings: true, + skipTemplates: false, + }, + ], + "jsonc/no-multi-str": "error", + "jsonc/no-nan": "error", + "jsonc/no-number-props": "error", + "jsonc/no-numeric-separators": "error", + "jsonc/no-octal": "error", + "jsonc/no-octal-escape": "error", + "jsonc/no-octal-numeric-literals": "error", + "jsonc/no-parenthesized": "error", + "jsonc/no-plus-sign": "error", + "jsonc/no-regexp-literals": "error", + "jsonc/no-sparse-arrays": "error", + "jsonc/no-template-literals": "error", + "jsonc/no-undefined-value": "error", + "jsonc/no-unicode-codepoint-escapes": "error", + "jsonc/no-useless-escape": "error", + "jsonc/object-curly-newline": ["off", "always"], + "jsonc/object-curly-spacing": [ + "error", + "always", + { + arraysInObjects: true, + objectsInObjects: true, + }, + ], + "jsonc/object-property-newline": [ + "error", + { + allowAllPropertiesOnSameLine: true, + }, + ], + "jsonc/quote-props": "error", + "jsonc/quotes": "error", + + // @see https://ota-meshi.github.io/eslint-plugin-jsonc/rules/sort-array-values.html + "jsonc/sort-array-values": [ + "warn", + { + order: { + type: "asc", + natural: true, + }, + pathPattern: ".*", + }, + ], + + // @see https://ota-meshi.github.io/eslint-plugin-jsonc/rules/sort-keys.html + "jsonc/sort-keys": [ + "warn", + { + order: { + type: "asc", + natural: true, + }, + pathPattern: ".*", + }, + ], + "jsonc/space-unary-ops": "error", + "jsonc/valid-json-number": "error", + "no-irregular-whitespace": "off", + "no-unused-expressions": "off", + "no-unused-vars": "off", + strict: "off", + }, + }, // @see https://ota-meshi.github.io/eslint-plugin-jsonc/rules/auto.html + { + extends: [...jsonc.configs["flat/recommended-with-json"]], + files: ["package.json"], + name: "@reliverse/config-eslint/json-package", + plugins: { + jsonc: jsonc, + }, + rules: { + "jsonc/array-bracket-newline": ["off", "always"], + "jsonc/sort-array-values": [ + "error", + { + order: { + type: "asc", + natural: true, + }, + pathPattern: "^files$", + }, + ], + "jsonc/sort-keys": [ + "warn", + { + order: [ + "name", + "displayName", + "version", + "private", + "description", + "categories", + "keywords", + "homepage", + "bugs", + "repository", + "funding", + "license", + "qna", + "author", + "maintainers", + "contributors", + "publisher", + "sideEffects", + "type", + "imports", + "exports", + "main", + "svelte", + "umd:main", + "jsdelivr", + "unpkg", + "module", + "source", + "jsnext:main", + "browser", + "react-native", + "types", + "typesVersions", + "typings", + "style", + "example", + "examplestyle", + "assets", + "bin", + "man", + "directories", + "files", + "workspaces", + "binary", + "scripts", + "betterScripts", + "contributes", + "activationEvent", + "husky", + "simple-git-hooks", + "pre-commit", + "commitlint", + "lint-staged", + "nano-staged", + "config", + "nodemonConfig", + "browserify", + "babel", + "browserslist", + "xo", + "prettier", + "eslintConfig", + "eslintIgnore", + "npmpackagejsonlint", + "release", + "remarkConfig", + "stylelint", + "ava", + "jest", + "mocha", + "nyc", + "tap", + "oclif", + "resolutions", + "dependencies", + "devDependencies", + "dependenciesMeta", + "peerDependencies", + "peerDependenciesMeta", + "optionalDependencies", + "bundledDependencies", + "bundleDependencies", + "extensionPack", + "extensionDependencies", + "flat", + "packageManager", + "engines", + "engineStrict", + "volta", + "languageName", + "os", + "cpu", + "preferGlobal", + "publishConfig", + "icon", + "badges", + "galleryBanner", + "preview", + "markdown", + "pnpm", + ], + pathPattern: "^$", + }, + { + order: { + type: "asc", + natural: false, + }, + pathPattern: "^(?:dev|peer|optional|bundled)?[Dd]ependencies(Meta)?$", + }, + { + order: { + type: "asc", + natural: true, + }, + pathPattern: "^(?:resolutions|overrides|pnpm.overrides)$", + }, + { + order: { + type: "asc", + natural: true, + }, + pathPattern: "^scripts.*$", + }, + { + order: ["types", "import", "require", "default"], + pathPattern: "^exports.*$", + }, + { + order: [ + "pre-applypatch", + "applypatch-msg", + "post-applypatch", + "pre-commit", + "pre-merge-commit", + "prepare-commit-msg", + "commit-msg", + "post-commit", + "pre-rebase", + "post-checkout", + "post-merge", + "pre-push", + "pre-receive", + "update", + "post-receive", + "post-update", + "push-to-checkout", + "pre-auto-gc", + "post-rewrite", + "sendemail-validate", + "fsmonitor-watchman", + "p4-pre-submit", + "post-index-chang", + ], + pathPattern: "^(?:gitHooks|husky|simple-git-hooks)$", + }, + { + order: ["url", "email"], + pathPattern: "bugs", + }, + { + order: ["type", "url"], + pathPattern: "(repository|funding|license)", + }, + { + order: ["name", "email", "url"], + pathPattern: "author", + }, + ], + }, + }, // @see https://ota-meshi.github.io/eslint-plugin-jsonc/rules + { + extends: [...jsonc.configs["flat/recommended-with-json"]], + files: ["tsconfig.json"], + name: "@reliverse/config-eslint/json-tsconfig", + plugins: { + jsonc: jsonc, + }, + rules: { + "jsonc/sort-keys": [ + "warn", + { + order: [ + "extends", + "compilerOptions", + "references", + "files", + "include", + "exclude", + ], + pathPattern: "^$", + }, + { + order: [ + // Projects + "incremental", + "composite", + "tsBuildInfoFile", + "disableSourceOfProjectReferenceRedirect", + "disableSolutionSearching", + "disableReferencedProjectLoad", + + // Language and Environment + "target", + "jsx", + "jsxFactory", + "jsxFragmentFactory", + "jsxImportSource", + "lib", + "moduleDetection", + "noLib", + "reactNamespace", + "useDefineForClassFields", + "emitDecoratorMetadata", + "experimentalDecorators", + + // Modules + "baseUrl", + "rootDir", + "rootDirs", + "customConditions", + "module", + "moduleResolution", + "moduleSuffixes", + "noResolve", + "paths", + "resolveJsonModule", + "resolvePackageJsonExports", + "resolvePackageJsonImports", + "typeRoots", + "types", + "allowArbitraryExtensions", + "allowImportingTsExtensions", + "allowUmdGlobalAccess", + + // JavaScript Support + "allowJs", + "checkJs", + "maxNodeModuleJsDepth", + + // Type Checking + "strict", + "strictBindCallApply", + "strictFunctionTypes", + "strictNullChecks", + "strictPropertyInitialization", + "allowUnreachableCode", + "allowUnusedLabels", + "alwaysStrict", + "exactOptionalPropertyTypes", + "noFallthroughCasesInSwitch", + "noImplicitAny", + "noImplicitOverride", + "noImplicitReturns", + "noImplicitThis", + "noPropertyAccessFromIndexSignature", + "noUncheckedIndexedAccess", + "noUnusedLocals", + "noUnusedParameters", + "useUnknownInCatchVariables", + + // Emit + "declaration", + "declarationDir", + "declarationMap", + "downlevelIteration", + "emitBOM", + "emitDeclarationOnly", + "importHelpers", + "importsNotUsedAsValues", + "inlineSourceMap", + "inlineSources", + "mapRoot", + "newLine", + "noEmit", + "noEmitHelpers", + "noEmitOnError", + "outDir", + "outFile", + "preserveConstEnums", + "preserveValueImports", + "removeComments", + "sourceMap", + "sourceRoot", + "stripInternal", + + // Interop Constraints + "allowSyntheticDefaultImports", + "esModuleInterop", + "forceConsistentCasingInFileNames", + "isolatedModules", + "preserveSymlinks", + "verbatimModuleSyntax", + + // Completeness + "skipDefaultLibCheck", + "skipLibCheck", + ], + pathPattern: "^compilerOptions$", + }, + ], + }, + }, // @see https://cspell.org + { + files: ["**/cspell.json"], + name: "@reliverse/config-eslint/addons-cspell", + rules: { + "jsonc/sort-array-values": "off", + "jsonc/sort-keys": "off", + }, + }, // @see https://github.com/coderaiser/putout#readme + { + files: [ + "**/.putout.json", + "**/.putout.recommended.json", + "**/.putout.rules-disabled.json", + ], + name: "@reliverse/config-eslint/addons-putout", + rules: { + "jsonc/sort-array-values": "off", + "jsonc/sort-keys": "off", + }, + }, // @see https://github.com/blefnk/reliverse-website-builder + { + files: ["addons/**/*.{ts,tsx}"], + name: "@reliverse/config-eslint/addons-core", + rules: { + "no-console": "off", + "no-relative-import-paths/no-relative-import-paths": [ + "error", + { + allowSameFolder: false, + prefix: "@", + rootDir: "addons", + }, + ], + "no-restricted-syntax": [ + "error", + { + message: "Potential circular dependency found", + selector: 'ImportDeclaration[source.value="."]', + }, + { + // message: "Unexpected console statement. Please run `pnpm codemod:reli` and apply `console-to-consola`.", + message: + "Unexpected console statement. Please import and use `consola` library instead.", + selector: + "CallExpression[callee.object.name=console][callee.property.name=/^(log|info|warn|error|trace)$/]", + }, + { + message: + "Unexpected `consola.log` statement. Remove it or use `info|warn|error|trace|success|...` instead.", + selector: + "CallExpression[callee.object.name=consola][callee.property.name=/^(log)$/]", + }, + ], // { + // message: "no optional", + // selector: "TSPropertySignature[optional=true]", + // }, + // { message: "no else", selector: "IfStatement[alternate]" }, + // { message: "no let", selector: "VariableDeclaration[kind=let]" }, + "unicorn/no-process-exit": "off", + }, + }, // @see https://remotion.dev/docs/contributing/formatting#eslint + { + files: ["addons/reliverse/remotion/*.{ts,tsx}"], + name: "@reliverse/config-eslint/addons-remotion", + rules: { + "no-relative-import-paths/no-relative-import-paths": "off", + }, + }, // @see https://eslint.org/docs/latest/use/configure + { + files: [ + "eslint.config.js", + "eslint.config.recommended.js", + "eslint.config.rules-disabled.js", + ], + name: "@reliverse/config-eslint/addons-eslint", + rules: { + "max-lines": "off", + }, + }, // @see https://npmjs.com/package/eslint-plugin-no-comments + { + files: ["addons/reliverse/template/index.ts"], + name: "@reliverse/config-eslint/addons-no-comments", + plugins: { + "no-comments": noComments, + }, + rules: { + "no-comments/disallowComments": "warn", + }, + }, +); diff --git a/addons/cluster/temp/eslint.config.deprecated.c.txt b/addons/cluster/temp/eslint.config.deprecated.c.txt new file mode 100644 index 00000000..486876ad --- /dev/null +++ b/addons/cluster/temp/eslint.config.deprecated.c.txt @@ -0,0 +1,105 @@ +import { FlatCompat } from "@eslint/eslintrc";// const compat = new FlatCompat({ baseDirectory: String(currentDirname) });// @see https://npmjs.com/package/@types/eslint__eslintrc// const getPutoutConfig = (/** @type {string} */ name) =>// compat.config(putout.configs[name]);// ====================================================// import {safeAlign} from 'eslint-plugin-putout/config';// import { matchToFlat, createESLintConfig } from '@putout/eslint-flat';// const match = { "**/*.{ts,tsx,js,jsx}": { 'n/no-unsupported-features/node-builtins': 'off' } };// const putout = createESLintConfig([safeAlign, matchToFlat(match)]);// extends { ...putout }// ====================================================// @see https://dev.to/favourmark05/writing-clean-code-best-practices-and-principles-3amh// @see https://eslint.org/docs/latest/extend/custom-rules#profile-rule-performance// @see https://kittygiraudel.com/2024/06/01/from-eslint-and-prettier-to-biome// @see https://gist.github.com/wojteklu/73c6914cc446146b8b533c0988cf8d29// @see https://dev.to/thawkin3/eslint-warnings-are-an-anti-pattern-33np// @see https://freecodecamp.org/news/how-to-write-clean-code// @see https://sonarsource.com/solutions/clean-code// @see https://biomejs.dev/internals/philosophy + +//////////////////////////////////////////////////////////////////////////////// + +// ====================================================// ? The content below is deprecated/experimental,// ? and planned to be removed or added in v1.3.0// ====================================================// import json from "eslint-plugin-json";// @see https://npmjs.com/package/eslint-plugin-json// {// ...json.configs.recommended,// files: ["**/*.json"],// name: "@reliverse/config-eslint/json",// plugins: { json: json },// processor: "json/json",// rules: { "json/*": ["error", { allowComments: false }] },// },// ====================================================/* "jsonc/sort-array-values": [ + "error", + { + order: { type: "asc" }, + // Hits the files property + pathPattern: "^files$", + }, + { + order: [ + "eslint", + "eslintplugin", + "eslint-plugin", + { + // Fallback order + order: { type: "asc" }, + }, + ], + pathPattern: "^keywords$", // Hits the keywords property + }, +], */ +// "jsonc/sort-array-values": [// "error",// {// order: { type: "asc" },// pathPattern: "^files$",// },// ],// ====================================================// TODO: putout adds `/js/`, but we're using `default`/* +"@stylistic/js/array-bracket-spacing": "off", +"@stylistic/js/arrow-spacing": "off", +"@stylistic/js/brace-style": "off", +"@stylistic/js/comma-dangle": ["off", "always-multiline"], +"@stylistic/js/comma-spacing": "off", +"@stylistic/js/eol-last": ["off", "always"], +"@stylistic/js/func-call-spacing": "off", +"@stylistic/js/function-paren-newline": ["off", "multiline-arguments"], +"@stylistic/js/implicit-arrow-linebreak": "off", +"@stylistic/js/indent": ["off", 4], +"@stylistic/js/key-spacing": "off", +"@stylistic/js/linebreak-style": ["off", "unix"], +"@stylistic/js/lines-between-class-members": "off", +"@stylistic/js/newline-per-chained-call": "off", +"@stylistic/js/no-extra-parens": [ + "off", + "all", + { + enforceForSequenceExpressions: false, + }, +], +"@stylistic/js/no-extra-semi": "off", +"@stylistic/js/no-multi-spaces": "off", +"@stylistic/js/no-multiple-empty-lines": [ + "off", + { + max: 1, + maxBOF: 0, + }, +], +"@stylistic/js/no-trailing-spaces": [ + "off", + { + skipBlankLines: true, + }, +], +"@stylistic/js/object-curly-spacing": "off", +"@stylistic/js/operator-linebreak": [ + "off", + "after", + { + overrides: { + ":": "before", + "=": "none", + "?": "before", + "|": "before", + "||": "before", + }, + }, +], +"@stylistic/js/padded-blocks": ["off", "never"], +"@stylistic/js/padding-line-between-statements": "off", +"@stylistic/js/quote-props": ["off", "consistent-as-needed"], +"@stylistic/js/quotes": [ + "off", + "single", + { + allowTemplateLiterals: true, + }, +], +"@stylistic/js/semi": "off", +"@stylistic/js/space-before-blocks": "off", +"@stylistic/js/space-before-function-paren": [ + "off", + { + anonymous: "never", + asyncArrow: "always", + named: "never", + }, +], +"@stylistic/js/space-in-parens": "off", +"@stylistic/js/space-infix-ops": [ + "off", + { + int32Hint: false, + }, +], + +//////////////////////////////////////////////////////////////////////////////// + diff --git a/addons/cluster/temp/extend.txt b/addons/cluster/temp/extend.txt new file mode 100644 index 00000000..ff53e628 --- /dev/null +++ b/addons/cluster/temp/extend.txt @@ -0,0 +1,66 @@ +// 🔴 DEPRECATED AND POSSIBLY WILL BE REMOVED IN RELIVATOR 1.3.0 🔴 || +// ================================================================= || + +import { type Metadata } from "next"; +import { LocaleLayoutParams } from "~/types"; +import merge from "ts-deepmerge"; + +import { DEFAULT_METADATA } from "./default"; + +export type GenerateMetadata = ( + params: LocaleLayoutParams, +) => Metadata | Promise<Metadata>; + +/** + * Extended metadata types + */ +type SeoProps = Metadata & { + url?: string | URL; + image?: { + url: string | URL; + secureUrl?: string | URL; + alt?: string; + type?: string; + width?: string | number; + height?: string | number; + }; +}; + +/** + * Helper method to deep merge the SEO params from a given page + * with the default SEO params. + * + * This method also will use title and description in the OpenGraph and + * Twitter metadata, if not set + */ +export function seo({ url, image, ...metadata }: SeoProps = {/**/}): Metadata { + const title = metadata.title || DEFAULT_METADATA.title; + const description = metadata.description || DEFAULT_METADATA.description; + + metadata.openGraph = { + title: title || undefined, + description: description || undefined, + ...metadata.openGraph, + }; + + metadata.twitter = { + title: title || undefined, + description: description || undefined, + ...metadata.twitter, + }; + + if (url) { + metadata.openGraph.url = url; + metadata.alternates = { + canonical: url, + ...metadata.alternates, + }; + } + + if (image) { + metadata.openGraph.images = [image]; + metadata.twitter.images = [image]; + } + + return merge(DEFAULT_METADATA, metadata); +} diff --git a/addons/cluster/temp/features.tsx.txt b/addons/cluster/temp/features.tsx.txt new file mode 100644 index 00000000..ca01bfe0 --- /dev/null +++ b/addons/cluster/temp/features.tsx.txt @@ -0,0 +1,376 @@ +import type { ElementType } from "react"; +import { Balancer } from "react-wrap-balancer"; + +import { useTranslations } from "next-intl"; + +import { Separator } from "~/components/Primitives"; +import { cn } from "~/utils"; + +// export async function GithubStarsPlugin() { +// const githubStars = await getGithubStars(); +// consola.log("githubStars", githubStars); +// return ( +// <> +// {githubStars ? ( +// <Link +// href={siteConfig.links.github} +// rel="noreferrer noopener" +// target="_blank" +// > +// <Badge +// className={` +// rounded-lg border-2 border-zinc-900/10 px-3.5 py-1.5 text-sm +// font-medium +// dark:border-zinc-800 +// lg:text-base +// `} +// variant="outline" +// > +// <Github aria-label="GitHub" className="mr-2 size-3.5" /> +// Star Relivator v1.2.6 on GitHub ⭐ {githubStars}/1,500 +// </Badge> +// </Link> +// ) : ( +// <span>GitHub</span> +// )} +// </> +// ); +// } */ +// function OssFeaturesSection({ +// githubStars, +// }: { +// githubStars: null | number; +// }) { +// const t = useTranslations("landing"); +// return ( +// <section +// className={cn( +// "mx-auto", +// "my-14", +// "mt-4", +// "grid", +// "w-fit", +// "place-items-center", +// "items-center", +// "justify-center", +// "gap-6", +// "space-y-6", +// "rounded-lg", +// "border", +// "bg-card", +// "px-6", +// "py-14", +// "pb-20", +// "pt-4", +// "text-card-foreground", +// "shadow-sm", +// "transition-opacity", +// "hover:opacity-80", +// )} +// id="open-source-features" +// > +// <Balancer +// as="h2" +// className={cn( +// "mx-auto", +// "my-6", +// "flex", +// "max-w-xl", +// "font-medium", +// "text-lg", +// "font-medium", +// "leading-[1.1]", +// "tracking-tight", +// "text-muted-foreground", +// "sm:text-xl", +// )} +// > +// {t("open-source.title")} +// </Balancer> +// <Balancer +// as="p" +// className={cn( +// "mx-auto", +// "flex", +// "max-w-[85%]", +// "leading-normal", +// "text-muted-foreground", +// "sm:text-lg", +// "sm:leading-7", +// )} +// > +// {githubStars && ( +// <Link href={config.framework.repo}> +// <span> +// {t("open-source.stars", { +// count: githubStars, +// })} +// </span> +// </Link> +// )}{" "} +// {t("title")} {t("open-source.subtitle.first")} {t("features.subtitle")}{" "} +// {t("subtitle")} +// <br /> +// {t("open-source.subtitle.second")}{" "} +// <Link +// className="underline underline-offset-4" +// href={config.framework.repo} +// rel="noreferrer noopener" +// target="_blank" +// > +// GitHub +// </Link>{" "} +// and visit our{" "} +// <Link +// className="underline underline-offset-4" +// href="https://discord.gg/Pb8uKbwpsJ" +// rel="noreferrer noopener" +// target="_blank" +// > +// Discord +// </Link> +// . +// </Balancer> +// <Features /> +// </section> +// ); +// } +import { + Clock, + Files, + LayoutDashboard, + PlaneTakeoff, + QrCode, + Server, + ShoppingBag, + ToggleRight, +} from "lucide-react"; + +// export async function GithubStarsPlugin() { +// const githubStars = await getGithubStars(); +// consola.log("githubStars", githubStars); +// return ( +// <> +// {githubStars ? ( +// <Link +// href={siteConfig.links.github} +// rel="noreferrer noopener" +// target="_blank" +// > +// <Badge +// className={` +// rounded-lg border-2 border-zinc-900/10 px-3.5 py-1.5 text-sm +// font-medium +// dark:border-zinc-800 +// lg:text-base +// `} +// variant="outline" +// > +// <Github aria-label="GitHub" className="mr-2 size-3.5" /> +// Star Relivator v1.2.6 on GitHub ⭐ {githubStars}/1,500 +// </Badge> +// </Link> +// ) : ( +// <span>GitHub</span> +// )} +// </> +// ); +// } */ +// function OssFeaturesSection({ +// githubStars, +// }: { +// githubStars: null | number; +// }) { +// const t = useTranslations("landing"); +// return ( +// <section +// className={cn( +// "mx-auto", +// "my-14", +// "mt-4", +// "grid", +// "w-fit", +// "place-items-center", +// "items-center", +// "justify-center", +// "gap-6", +// "space-y-6", +// "rounded-lg", +// "border", +// "bg-card", +// "px-6", +// "py-14", +// "pb-20", +// "pt-4", +// "text-card-foreground", +// "shadow-sm", +// "transition-opacity", +// "hover:opacity-80", +// )} +// id="open-source-features" +// > +// <Balancer +// as="h2" +// className={cn( +// "mx-auto", +// "my-6", +// "flex", +// "max-w-xl", +// "font-medium", +// "text-lg", +// "font-medium", +// "leading-[1.1]", +// "tracking-tight", +// "text-muted-foreground", +// "sm:text-xl", +// )} +// > +// {t("open-source.title")} +// </Balancer> +// <Balancer +// as="p" +// className={cn( +// "mx-auto", +// "flex", +// "max-w-[85%]", +// "leading-normal", +// "text-muted-foreground", +// "sm:text-lg", +// "sm:leading-7", +// )} +// > +// {githubStars && ( +// <Link href={config.framework.repo}> +// <span> +// {t("open-source.stars", { +// count: githubStars, +// })} +// </span> +// </Link> +// )}{" "} +// {t("title")} {t("open-source.subtitle.first")} {t("features.subtitle")}{" "} +// {t("subtitle")} +// <br /> +// {t("open-source.subtitle.second")}{" "} +// <Link +// className="underline underline-offset-4" +// href={config.framework.repo} +// rel="noreferrer noopener" +// target="_blank" +// > +// GitHub +// </Link>{" "} +// and visit our{" "} +// <Link +// className="underline underline-offset-4" +// href="https://discord.gg/Pb8uKbwpsJ" +// rel="noreferrer noopener" +// target="_blank" +// > +// Discord +// </Link> +// . +// </Balancer> +// <Features /> +// </section> +// ); +// } +export function Features() { + const t = useTranslations("landing"); + + return ( + <div + className={` + mx-auto grid justify-center gap-4 + + lg:grid-cols-4 + + md:grid-cols-2 + + sm:grid-cols-2 + `} + > + <FeatureCard + description={t("features.devtools.ambitions-description")} + icon={Clock} + title={t("features.files.roadmap")} + /> + <FeatureCard + description={t("features.files.on-the-fly-description")} + icon={PlaneTakeoff} + title={t("features.files.on-the-fly")} + /> + <FeatureCard + description={t("features.cryptography.description")} + icon={QrCode} + title={t("features.cryptography.title")} + /> + <FeatureCard + description={t("features.text.description")} + icon={ToggleRight} + title={t("features.text.title")} + /> + <FeatureCard + description={t("features.files.description")} + icon={Files} + title={t("features.files.title")} + /> + <FeatureCard + description={t("features.clock.description")} + icon={Server} + title={t("features.clock.title")} + /> + <FeatureCard + description={t("features.currency.description")} + icon={LayoutDashboard} + title={t("features.currency.title")} + /> + <FeatureCard + description={t("features.devtools.description")} + icon={ShoppingBag} + title={t("features.devtools.title")} + /> + </div> + ); +} + +type FeatureCardProps = { + description: string; + title: string; + icon: ElementType; +}; + +function FeatureCard({ + description, + title, + icon: Icon, +}: FeatureCardProps) { + return ( + <div + className={` + overflow-hidden rounded-lg border bg-background p-2 + text-left + `} + > + <div className="flex flex-col justify-between rounded-lg p-6"> + <div className="flex min-h-[64px] items-center space-x-4"> + <Icon aria-hidden className="size-8" /> + <Balancer + as="h2" + className={cn(` + text-lg font-medium tracking-tight text-muted-foreground + + sm:text-xl + `)} + > + {title} + </Balancer> + </div> + <Separator className="my-4" /> + <Balancer as="p" className="flex text-muted-foreground"> + {description} + </Balancer> + </div> + </div> + ); +} diff --git a/addons/cluster/temp/h-variants.tsx.txt b/addons/cluster/temp/h-variants.tsx.txt new file mode 100644 index 00000000..de90835f --- /dev/null +++ b/addons/cluster/temp/h-variants.tsx.txt @@ -0,0 +1,47 @@ +import type { PlateElementProps } from "@udecode/plate-common"; +import type { VariantProps } from "class-variance-authority"; + +import { PlateElement } from "@udecode/plate-common"; +import { cva } from "class-variance-authority"; + +const headingVariants = cva("", { + variants: { + isFirstBlock: { + false: "", + true: "!mt-0", + }, + variant: { + h1: "mb-1 mt-[2em] text-4xl font-bold", + h2: "mb-px mt-[1.4em] text-2xl font-semibold tracking-tight", + h3: "mb-px mt-[1em] text-xl font-semibold tracking-tight", + h4: "mt-[0.75em] text-lg font-semibold tracking-tight", + h5: "mt-[0.75em] text-lg font-semibold tracking-tight", + h6: "mt-[0.75em] text-base font-semibold tracking-tight", + }, + }, +}); + +export function HeadingElement({ + children, + className, + variant = "h1", + ...props +}: PlateElementProps & VariantProps<typeof headingVariants>) { + const { editor, element } = props; + + const Element = variant!; + + return ( + <PlateElement + asChild + className={headingVariants({ + className, + isFirstBlock: element === editor.children[0], + variant, + })} + {...props} + > + <Element>{children}</Element> + </PlateElement> + ); +} diff --git a/addons/cluster/temp/index-db.ts.txt b/addons/cluster/temp/index-db.ts.txt new file mode 100644 index 00000000..1aea83ba --- /dev/null +++ b/addons/cluster/temp/index-db.ts.txt @@ -0,0 +1,136 @@ +// 🔴 DEPRECATED AND POSSIBLY WILL BE REMOVED IN RELIVATOR 1.3.0 🔴 || +// ================================================================= || + +/** + * Unified Schema Exporter for Multiple Databases + * ============================================== + * Please check the "schema/index.ts" file for + * instructions, resources, inspirations, etc. + */ + +import { Client as ClientPlanetscale } from "@planetscale/database"; +import { + drizzle as drizzlePlanetscale, + type PlanetScaleDatabase, +} from "drizzle-orm/planetscale-serverless"; +import { + drizzle as drizzlePostgres, + type PostgresJsDatabase, +} from "drizzle-orm/postgres-js"; +import postgres from "postgres"; + +import { env } from "~/env"; +import { addQueryParamIfMissed } from "~/utils"; + +import * as schemaMysql from "./schema/mysql"; +import * as schemaPgsql from "./schema/pgsql"; + +// Connection strings for MySQL and PostgreSQL +// Add the ssl query parameter if it's missing +let csMysql: string = addQueryParamIfMissed( + env.DATABASE_URL, + "ssl", + JSON.stringify({ rejectUnauthorized: true }), +); +let csPgsql: string = addQueryParamIfMissed( + env.DATABASE_URL, + "sslmode", + "require", +); + +// todo: we need to figure out +// todo: how to type db properly +let db: + | PlanetScaleDatabase<typeof schemaMysql> + | PostgresJsDatabase<typeof schemaPgsql> + | any; + +let dbProvider = env.NEXT_PUBLIC_DB_PROVIDER; + +let postgresOption:postgres.Options<{/**/}> = { ssl: "allow", max: 1 }; + +// Configure this based on the database provider. +// Feel free to add/remove/edit things if needed. +try { + // Set default DB provider based on DATABASE_URL + // if NEXT_PUBLIC_DB_PROVIDER is not specified + if (!dbProvider) { + const databaseUrl = env.DATABASE_URL; + if (databaseUrl?.startsWith("mysql://")) { + dbProvider = "private-mysql"; + csMysql = env.DATABASE_URL; + } + else if (databaseUrl?.startsWith("postgres://")){ + dbProvider = "private-postgres"; + postgresOption = {/**/}; + csPgsql = env.DATABASE_URL; + } + } + + switch (dbProvider) { + case "private-mysql": + case "planetscale": + const clientPlanetscale = new ClientPlanetscale({ + host: process.env["DATABASE_HOST"], + username: process.env["DATABASE_USERNAME"], + password: process.env["DATABASE_PASSWORD"], + }); + db = drizzlePlanetscale(clientPlanetscale, { + schema: schemaMysql, + logger: false, + }); + break; + } + case "railway": { + if (env.DATABASE_URL?.startsWith("mysql://")) { + const clientPlanetscale = new ClientPlanetscale({ + host: process.env["DATABASE_HOST"], + username: process.env["DATABASE_USERNAME"], + password: process.env["DATABASE_PASSWORD"], + }); + db = drizzlePlanetscale(clientPlanetscale, { + schema: schemaMysql, + logger: false, + }); + } else if (env.DATABASE_URL?.startsWith("postgres://")) { + db = drizzlePostgres(postgres(csPgsql, { ssl: "allow", max: 1 }), { + schema: schemaPgsql, + logger: false, + }); + } else { + throw new Error( + `❌ Unsupported DATABASE_URL for NEXT_PUBLIC_DB_PROVIDER 'railway'. \ + Verify the environment configuration.`, + ); + } + break; + } + case "railway": + case "vercel": + case "neon": + case "private-postgres": { + db = drizzlePostgres(postgres(csPgsql, { ssl: "allow", max: 1 }), { + schema: schemaPgsql, + logger: false, + }); + break; + } + default: + throw new Error( + `❌ Unsupported NEXT_PUBLIC_DB_PROVIDER "${dbProvider}". \ + Please check the environment configuration.`, + ); + } +} catch (error) { + if (error instanceof Error) { + consola.error(error.message); + process.exit(1); + } else { + // If for any reason something else was + // thrown that wasn't an Error, handle it + consola.error("❌ An unexpected error occurred:", error); + process.exit(1); // Exits the process with a failure code + } +} + +export { db }; diff --git a/addons/cluster/temp/index-schema.ts.txt b/addons/cluster/temp/index-schema.ts.txt new file mode 100644 index 00000000..a862aa50 --- /dev/null +++ b/addons/cluster/temp/index-schema.ts.txt @@ -0,0 +1,118 @@ +// 🔴 DEPRECATED AND POSSIBLY WILL BE REMOVED IN RELIVATOR 1.3.0 🔴 || +// ================================================================= || + +/** + * Unified Schema Exporter for Multiple Databases + * ============================================== + * + * Supports both MySQL and PostgreSQL, enabling consistent table imports across the app. + * Uses environment variables for schema selection, compatible with PlanetScale, Neon, Vercel, + * and Railway providers. Aims to maintain schema consistency and minimize table mismatches. + * + * Includes instructions, resources, inspirations at the end of file. + * Check {dialect}.ts files to see the detailed database structures. + */ + + +import * as schemaMysql from "~/db/schema/mysql"; +import * as schemaPgsql from "~/db/schema/pgsql"; +import { env } from "~/env"; + +let dbProvider = env.NEXT_PUBLIC_DB_PROVIDER || "sqlite"; + +if (!dbProvider) { + const databaseUrl = process.env.DATABASE_URL; + if (!databaseUrl){ + consola.error( + "❌ DATABASE_URL is not set (refer to .env.example)", + ); + } else { + if (databaseUrl?.startsWith("mysql://")) dbProvider = "private-mysql"; + else if (databaseUrl?.startsWith("postgres://")) dbProvider = "private-postgres"; +} + +// Configure this based on the database provider. +// Feel free to add/remove/edit things if needed. +const selectedSchema = (() => { + // Assign schema based on the dbProvider and railwayMode + if (["planetscale","private-mysql"].includes(dbProvider)) { + return schemaMysql; + } else if (["railway"].includes(dbProvider)) { + return railwayMode === "mysql" ? schemaMysql : schemaPgsql; + } else if (["neon","vercel","private-postgres"].includes(dbProvider)) { + return schemaPgsql; + } else { + consola.error("❌ selectedSchema(): Unknown NEXT_PUBLIC_DB_PROVIDER"); + return schemaMysql; + } +})(); + +// ======================================================= +// Export tables based on the selected schema +// ======================================================= + +export const { + accounts, + addresses, + carts, + emails, + orders, + payments, + products, + sessions, + stores, + stripeEvent, + todos, + users, + verificationTokens, +} = selectedSchema; + +// ======================================================= +// Export types based on the selected schema +// ======================================================= + +export type Address = typeof selectedSchema.addresses.$inferSelect; +export type Cart = typeof selectedSchema.carts.$inferSelect; +export type EmailPref = typeof selectedSchema.emails.$inferSelect; +export type Order = typeof selectedSchema.orders.$inferSelect; +export type Payment = typeof selectedSchema.payments.$inferSelect; +export type Product = typeof selectedSchema.products.$inferSelect; +export type Store = typeof selectedSchema.stores.$inferSelect; +export type Todo = typeof selectedSchema.todos.$inferSelect; +export type User = typeof selectedSchema.users.$inferSelect; + +/** + * Useful Database Commands: + * – [Apply changes] pnpm mysql:push | pnpm pg:push + * – [Generate migrations] pnpm mysql:generate | pnpm pg:generate + * – [Execute generated migrations (not tested too much)] pg db:migrate + * These commands facilitate managing database schema changes and migrations + * across different environments, ensuring a streamlined development process. + * + * Learning resources: + * @see https://authjs.dev/concepts/faq + * @see https://authjs.dev/ref/adapter/drizzle + * @see https://github.com/bs-oss/drizzle-orm-mono + * @see https://neon.tech/docs/serverless/serverless-driver + * @see https://orm.drizzle.team/docs/column-types/mysql + * @see https://orm.drizzle.team/docs/column-types/pg + * @see https://orm.drizzle.team/docs/custom-types + * @see https://orm.drizzle.team/docs/goodies + * @see https://orm.drizzle.team/docs/quick-mysql/planetscale + * @see https://orm.drizzle.team/docs/quick-start + * @see https://orm.drizzle.team/docs/sql-schema-declaration + * @see https://youtu.be/qclv0iaq9zu + * + * Inspirations: + * @see https://github.com/Alissonsleal/brapi/blob/main/db/schemas/tables/stripeEvent.ts + * @see https://github.com/apestein/nextflix/blob/main/src/db/schema.ts + * @see https://github.com/CodeWitchBella/songbook/blob/main/workers/src/db/drizzle.ts + * @see https://github.com/georgwittberger/next-app-router-template/blob/main/src/schemas/todos.ts + * @see https://github.com/jackblatch/onestopshop/blob/main/db/schema.ts + * @see https://github.com/jherr/trpc-on-the-app-router/blob/main/src/db/schema.ts + * @see https://github.com/mrevanzak/vivat-marketplace/blob/main/packages/db/schema/product.ts + * @see https://github.com/rexfordessilfie/next-auth-account-linking/blob/main/src/lib/db/schema.ts + * @see https://github.com/ryanmearns/taskify/blob/main/apps/app/src/db/schema.ts + * @see https://github.com/sadmann7/skateshop/blob/main/src/db/schema.ts + * @see https://github.com/saga-sanga/todo-trpc/blob/main/src/db/schema.ts + */ diff --git a/addons/cluster/temp/instrumentation.ts.txt b/addons/cluster/temp/instrumentation.ts.txt new file mode 100644 index 00000000..e6343360 --- /dev/null +++ b/addons/cluster/temp/instrumentation.ts.txt @@ -0,0 +1,26 @@ +// @see https://nextjs.org/docs/app/building-the-application/optimizing/instrumentation + +export async function register() { + // @see https://baselime.io/docs/sending-data/languages/next.js + // eslint-disable-next-line no-restricted-props + if (process.env.NEXT_RUNTIME === "nodejs") { + const { BaselimeSDK, BetterHttpInstrumentation, VercelPlugin } = await import("@baselime/node-opentelemetry"); + + const sdk = new BaselimeSDK({ + instrumentations: [ + new BetterHttpInstrumentation({ + plugins: [ + // We need to add the Vercel plugin to enable correlation + // between logs and traces for projects deployed on Vercel + new VercelPlugin(), + ], + }), + ], + + serverless: true, + service: "my-service", + }); + + sdk.start(); + } +} diff --git a/addons/cluster/temp/intl.ts.txt b/addons/cluster/temp/intl.ts.txt new file mode 100644 index 00000000..10a8f6e5 --- /dev/null +++ b/addons/cluster/temp/intl.ts.txt @@ -0,0 +1,56 @@ +import type { AbstractIntlMessages } from "next-intl"; + +import deepmerge from "deepmerge"; +import { notFound } from "next/navigation"; +import { getRequestConfig } from "next-intl/server"; + +import { locales } from "~/config/navigation"; +import de_de from "~/messages/de.json"; +import en_us from "~/messages/en.json"; +import es_es from "~/messages/es.json"; +import fa_ir from "~/messages/fa.json"; +import fr_fr from "~/messages/fr.json"; +import hi_in from "~/messages/hi.json"; +import it_it from "~/messages/it.json"; +import pl_pl from "~/messages/pl.json"; +import tr_tr from "~/messages/tr.json"; +import uk_ua from "~/messages/uk.json"; +import zh_cn from "~/messages/zh.json"; + +// Create a mapping from locale identifiers +// to the specific imported JSON modules +const localesList = { + de: de_de, + en: en_us, + es: es_es, + fa: fa_ir, + fr: fr_fr, + hi: hi_in, + pl: pl_pl, + test: it_it, + tr: tr_tr, + uk: uk_ua, + zh: zh_cn, +} as const; + +// Exporting default function that asynchronously receives +// the locale object and returns the configuration object +export default getRequestConfig(async ({ locale }) => { + if (!locales.includes(locale)) { notFound(); } + + // Load messages for the current locale + const primaryMessages: AbstractIntlMessages = + (await localesList[locale]) || localesList.en; + + // Load messages for the fallback locale + const fallbackMessages: AbstractIntlMessages = localesList.en; + + // Merge primary locale messages with fallback locale messages + const messages = deepmerge(fallbackMessages, primaryMessages); + + // When using Turbopack we enable HMR for locale + // This approach also works fine without --turbo + return { + messages, + }; +}); // When not using next dev --turbo, we can simplify imports:// export default getRequestConfig(async ({ locale }) => ({// messages: (await import(`./messages/${locale}.json`)).default,// }));// Learn more and resources// ========================// @see https://next-intl-docs.vercel.app/docs/environments/server-client-components// @see https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md// @see https://next-intl-docs.vercel.app/docs/getting-started/app-router// @see https://github.com/amannn/next-intl/issues?q=turbo// @see https://github.com/amannn/next-intl/issues/718// @see https://github.com/amannn/next-intl/pull/641// @see https://github.com/vercel/turbo/issues/2372/ diff --git a/addons/cluster/temp/jest.config.txt b/addons/cluster/temp/jest.config.txt new file mode 100644 index 00000000..555c91c3 --- /dev/null +++ b/addons/cluster/temp/jest.config.txt @@ -0,0 +1,36 @@ +// 🔴 DEPRECATED AND POSSIBLY WILL BE REMOVED IN RELIVATOR 1.3.0 🔴 || +// ================================================================= || + +/** + * Jest Testing Configuration + * ========================== + * + * @see https://nextjs.org/docs/architecture/nextjs-compiler#jest + * @see https://jest-extended.jestcommunity.dev/docs/getting-started/setup + * @see https://github.com/MichalLytek/type-graphql/blob/master/jest.config.ts + */ + +import nextJest from "next/jest"; + +const createJestConfig = nextJest; + +const customJestConfig = { + rootDir: "./", + verbose: false, + preset: "ts-jest", + collectCoverage: false, + testEnvironment: "node", + extensionsToTreatAsEsm: [".ts"], + coverageDirectory: "<rootDir>/coverage", + setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"], + moduleNameMapper: { "^~/(.*)$": "<rootDir>/$1" }, + moduleDirectories: ["node_modules", "<rootDir>/"], + roots: ["<rootDir>/src", "<rootDir>/src/tests/jest"], + moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], + collectCoverageFrom: ["<rootDir>/src/**/*.ts", "!<rootDir>/src/**/*.d.ts"], + transform: { "^.+\\.tsx?$": ["ts-jest", { tsconfig: "./tsconfig.json" }] }, +}; + +// createJestConfig is exported this way to ensure that +// next/jest can load the nextjs config which is async +module.exports = createJestConfig(); diff --git a/addons/cluster/temp/jest.setup.ts.txt b/addons/cluster/temp/jest.setup.ts.txt new file mode 100644 index 00000000..c0054f30 --- /dev/null +++ b/addons/cluster/temp/jest.setup.ts.txt @@ -0,0 +1,17 @@ +/** + * Jest Setup Configuration + * ========================== + * + * @see https://nextjs.org/docs/architecture/nextjs-compiler#jest + * @see https://jest-extended.jestcommunity.dev/docs/getting-started/setup + */ + +import * as matchers from "jest-extended"; + +import "@testing-library/jest-dom"; + +expect.extend(matchers); + +// 🔴 DEPRECATED AND POSSIBLY WILL BE REMOVED IN RELIVATOR 1.3.0 🔴 || +// Starting Relivator 1.3.0, it can be added by using pnpm reliverse || +// ================================================================= || diff --git a/addons/cluster/temp/json.ts.txt b/addons/cluster/temp/json.ts.txt new file mode 100644 index 00000000..2bedd3ee --- /dev/null +++ b/addons/cluster/temp/json.ts.txt @@ -0,0 +1,177 @@ +import type { ApptsConfig } from "@/reliverse/relicon/setup/types"; + +import { writeFile } from "fs/promises"; + +import { confirm, text } from "@clack/prompts"; +import { loadConfig } from "c12"; +import consola from "consola"; + +// !! TODO: UNFINISHED !! PLEASE CHECK `../appts.ts` FILE FOR .TS VERSION +export async function configureApptsJson({ apptsConfig }: ApptsConfig) { + const { config: currentConfig } = await loadConfig({ + name: "appts", // Should match config file name without extension + cwd: apptsConfig, + dotenv: true, + }); + + if (!currentConfig) { + consola.error( + "Something went wrong! Configuration file `src/config/json/appts.json` was not found!", + ); + + return; + } + + const proceed = await confirm({ + initialValue: false, + message: + "[⚙️ Advanced]: Do you want to configure the appts.json file? (NOTE: this file is currently unused by the app)", + }); + + if (typeof proceed !== "boolean" || !proceed) { + return; + } + + const handle = await askForHandle(currentConfig.author?.handle); + + const prompts = [ + { + key: "name", + default: "Relivator", + message: "What is the short name of the app?", + }, + { + key: "appNameDesc", + default: "Relivator: Next.js 15 and React 19 template by Reliverse", + message: "What is the SEO name of the app?", + }, + { + key: "appPublisher", + default: "Reliverse", + message: "Who is the publisher of the app?", + }, + { + key: "appVersion", + default: "1.2.6", + message: "What is the version of the app?", + }, + { + key: "authorEmail", + default: "blefnk@gmail.com", + message: "What is the author's email?", + }, + { + key: "authorFullName", + default: "Nazar Kornienko", + message: "What is the author's full name?", + }, + { + key: "authorUrl", + default: "https://github.com/blefnk", + message: "What is the author's URL?", + }, + ]; + + const results: Record<string, string> = {}; + + for (const prompt of prompts) { + results[prompt.key] = await askForText( + prompt.message, + currentConfig[prompt.key] ?? prompt.default, + ); + } + + const { + name, + appNameDesc, + appPublisher, + appVersion, + authorEmail, + authorFullName, + authorUrl, + } = results; + + if ( + [ + handle, + name, + appNameDesc, + appPublisher, + appVersion, + authorEmail, + authorFullName, + authorUrl, + ].some((value) => typeof value !== "string") + ) { + return; + } + + await updateFile(apptsConfig, { + name: name as string, + appNameDesc: appNameDesc as string, + appPublisher: appPublisher as string, + appVersion: appVersion as string, + authorEmail: authorEmail as string, + authorFullName: authorFullName as string, + authorUrl: authorUrl as string, + handle: handle as string, + }); +} + +async function askForHandle(currentHandle: string): Promise<string> { + return (await text({ + message: + // eslint-disable-next-line @stylistic/max-len + "Let's customize the Relivator Next.js template according to your needs. The `src/config/json/appts.json` file contains the main configuration for your product. Let's get acquainted! What's your handle (without the @ symbol)? Example:", + placeholder: currentHandle || "blefnk", + validate: (value) => { + if (value && !/^[\da-z]+$/i.test(value)) { + return "Please use only English letters and numbers."; + } + }, + })) as string; +} + +async function askForText( + message: string, + placeholder: string, +): Promise<string> { + return ( + ((await text({ + message, + placeholder, + validate: (value) => { + if (value === undefined || value === null) { + return `Please enter ${message.toLowerCase()}.`; + } + }, + })) as string) || placeholder + ); +} + +async function updateFile(filePath: string, config: Record<string, string>) { + try { + const updatedConfig = { + name: config.name, + appNameDesc: config.appNameDesc, + appPublisher: config.appPublisher, + appVersion: config.appVersion, + author: { + email: config.authorEmail, + fullName: config.authorFullName, + handle: config.handle, + handleAt: `@${config.handle}`, + url: config.authorUrl, + }, + }; + + const fileContent = JSON.stringify(updatedConfig, null, 2); + + await writeFile(filePath, fileContent, "utf8"); + consola.success( + "Configuration has been edited successfully! Please visit the file later to make any additional changes.", + ); + } catch (error) { + consola.error("Error updating configuration file content:", error); + } +} diff --git a/addons/cluster/temp/liveblocks-room.tsx.txt b/addons/cluster/temp/liveblocks-room.tsx.txt new file mode 100644 index 00000000..41075041 --- /dev/null +++ b/addons/cluster/temp/liveblocks-room.tsx.txt @@ -0,0 +1,37 @@ +"use client"; + +import { ReactNode } from "react"; + +import { ClientSideSuspense } from "@liveblocks/react"; + +import { env } from "~/env"; +import { RoomProvider } from "~/config/liveblocks.config"; + +// TODO: W.I.P. + +/** @see https://liveblocks.io/docs/get-started/nextjs */ + +export function Room({ children }: { children: ReactNode }) { + return ( + <> + {env.NEXT_PUBLIC_LIVEBLOCKS_PUBLIC_KEY ? ( + <RoomProvider + id="my-room" + // @ts-expect-error TODO: Fix + initialPresence={ + { + /* - */ + } + } + > + {/* <ClientSideSuspense fallback={<div>Loading…</div>}> */} + {/* <ClientSideSuspense fallback={<FakeLoadingVariantOne />}> */} + <ClientSideSuspense fallback={<>...</>}> + {( ) => + children} + </ClientSideSuspense> + </RoomProvider> + ) : null} + </> + ); +} diff --git a/addons/cluster/temp/liveblocks-showdown.tsx.txt b/addons/cluster/temp/liveblocks-showdown.tsx.txt new file mode 100644 index 00000000..9b03afe4 --- /dev/null +++ b/addons/cluster/temp/liveblocks-showdown.tsx.txt @@ -0,0 +1,289 @@ +"use client"; + +/** +import { startTransition, useEffect, useRef, useState } from 'react'; +import dynamic from "next/dynamic"; + +import { getCount } from "./live-visitors"; +*/ + +/** @see https://github.com/aidenybai/million/blob/main/website/components/extra-content.tsx */ + +/** + const LagRadar = dynamic(() => import("react-lag-radar"), { ssr: false }); + +export function ExtraContent() { + return ( + <> + <Status /> + </> + ); +} +*/ + +/** @see https://liveblocks.io/docs/get-started/nextjs */ + +/* export function Status() { + const [, forceUpdate] = useState({}); + const count = getCount(); + const userString = count > 1 ? "users are" : "user is"; + // useEffect(() => { + // const interval = setInterval(() => { + // forceUpdate({}); + // }, 1000); + // return () => clearInterval(interval); + // }, []); + return ( + <div className="ml-1 flex items-center gap-2 text-sm text-primary/70"> + <div className="relative flex h-3 w-3"> + <div className={cn( + "absolute", + "inline-flex", + "h-full", + "w-full", + "animate-ping", + "rounded-full", + "bg-zinc-500", + "opacity-75", + )}></div> + <div className="relative inline-flex h-3 w-3 rounded-full bg-zinc-600"></div> + </div> + Currently {count} other {userString} online. + </div> + ); +} */ + +/** + * Renders a showdown between React and Million.js, measuring the performance of each library. + * @param initStart - whether to start the showdown immediately or wait for a button click + * @param amount - the number of elements to render in the showdown + * @returns the rendered component + */ +// million-ignore +// export function Showdown({ initStart = false, amount = 1000 }) { +/** + * The current state of the showdown, indicating whether it has started or not + */ +// const [start, setStart] = useState<boolean>(false); +// * The number of times the showdown has been rendered +// */ +// const [renders, setRenders] = useState<number>(0); +// * A ref to the first update of the component, used // to prevent unnecessary re-renders +// */ +// const firstUpdate = useRef(true); +// * A ref to the functions used to render the // components +// */ +// const ref = useRef<{ +// renderReact: () => void; +// renderMillion: () => void; +// }>(); + +/** + * Sets up the showdown by importing the necessary libraries and creating the components + */ +/* useEffect(() => { + if (firstUpdate.current) { + firstUpdate.current = false; + return; + } + const setup = async () => { + const { block, mount, patch } = await import( + "./million-library/million.mjs" + ); + const { createRoot } = await import("react-dom/client"); + + const reactRoot = document.createElement("div"); + const millionRoot = document.createElement("div"); + + // An array filled with the specified number of elements + // eslint-disable-next-line unicorn/no-new-array + const filledArray = new Array(amount).fill(0); + + // A function that returns a block of JSX based on the specified text + // @param text - the text to display in the block + // @returns the block of JSX + const b = block(({ text }: { text: string }) => ({ + type: "div", + props: { + children: [ + { + type: "div", + props: { + children: [ + ...filledArray.map(() => ({ + type: "div", + props: { + children: filledArray.map(() => ({ + type: "div", + props: {}, + })), + }, + })), + { + type: "div", + props: { + children: [text], + }, + }, + ], + }, + }, + ], + }, + })); + + // A React component that displays the specified text in a block with nested divs + // @param text - the text to display + // @returns the React component + const Component = ({ text }) => { + return ( + <div> + <div> + {filledArray.map(() => ( + <div> + {filledArray.map(() => ( + // biome-ignore lint/style/useSelfClosingElements: <explanation> + <div></div> + ))} + </div> + ))} + <div>{text}</div> + </div> + </div> + ); + }; + + // The current block of JSX, used to update the Million.js component + const currentBlock = b({ text: String(Math.random()) }); + // Mounts the current block of JSX to the Million.js component + mount(currentBlock, millionRoot); + // Creates the React root element and renders the initial component + const root = createRoot(reactRoot); + + root.render(<Component text={String(Math.random())} />); + + // Sets the render functions for the React and Million.js components + ref.current = { + renderReact() { + root.render(<Component text={String(Math.random())} />); + }, + renderMillion() { + patch(currentBlock, b({ text: String(Math.random()) })); + }, + }; + }; + if (initStart || start) { + startTransition(() => { + void setup(); + }); + } + }, [start]); */ + +/** + * Starts the showdown if it is not already running and initializes the state + */ +/* useEffect(() => { + if (initStart) { + setTimeout(() => { + startTransition(() => { + setStart(true); + }); + }, 1000); + } + }, []); + + return ( + <div className={cn( + "w-full", + "rounded-lg", + "border", + "border-[#e5e7eb]", + "bg-white", + "p-3", + "shadow", + "dark:border-[#262626]", + "dark:bg-[#141414]", + )}> + <div className="px-2 text-xs font-semibold text-[#6b7280]"> + SPEED SHOWDOWN ⚡ + </div> + <hr className="my-2 border-[#e8e8e8] dark:border-[#4b4b4b]" /> + {!start ? ( + <div> + <button + type="button" + onClick={() => setStart(true)} + className={cn( + "w-full", + "rounded", + "bg-[#f0e1ff]", + "p-2", + "font-bold", + "text-[#5200a3]", + "dark:bg-[#24182f]", + "dark:text-[#9580ff]", + )} + > + {initStart ? "Booting up demo..." : "Begin"} + </button> + </div> + ) : ( + <div> + <div className="px-2 text-xs text-[#6b7280]"> + Click on a button to invoke a render cycle{" "} + <span className="font-semibold">({renders} renders)</span> + </div> + <div className="my-4 flex justify-center"> + <LagRadar /> + </div> + <div className="mb-4 flex justify-center gap-2"> + <button + type="button" + onClick={() => { + setRenders(renders + 1); + ref.current?.renderMillion(); + }} + className={cn( + "rounded-full", + "bg-[#b073d9]", + "px-4", + "py-2", + "font-bold", + "text-white", + "shadow", + "transition-all", + "hover:opacity-90", + "active:scale-105", + "active:opacity-90", + )} + > + Million.js + </button> + <button + type="button" + onClick={() => { + setRenders(renders + 1); + ref.current?.renderReact(); + }} + className={cn( + "rounded-full", + "bg-[#139eca]", + "px-4", + "py-2", + "text-white", + "shadow", + "transition-all", + "hover:opacity-90", + "active:scale-105", + "active:opacity-90", + )} + > + React + </button> + </div> + </div> + )} + </div> + ); +} + */ diff --git a/addons/cluster/temp/liveblocks-users-online.tsx.txt b/addons/cluster/temp/liveblocks-users-online.tsx.txt new file mode 100644 index 00000000..6ef2c24a --- /dev/null +++ b/addons/cluster/temp/liveblocks-users-online.tsx.txt @@ -0,0 +1,58 @@ +"use client"; + +import { getCount } from "~/components/Common/visitors-cursors"; +import { cn } from "~/utils"; + +/** @see https://github.com/aidenybai/million/blob/main/website/components/extra-content.tsx */ + +// const LagRadar = dynamic(() => import("react-lag-radar"), { +// ssr: false, +// }); + +export function ExtraContent( ) { + return <Status />; +} + +/** @see https://liveblocks.io/docs/get-started/nextjs */ + +export function Status( ) { + // const [, forceUpdate] = useState({ + /**/ + // }); + const count = getCount( ); + const userString = count > 1 ? "users are" : "user is"; + + // useEffect(() => { + // const interval = setInterval(() => { + // forceUpdate({/**/}); + // }, 1000); + // return () => clearInterval(interval); + // }, []); + return ( + <div className="ml-1 flex items-center gap-2 text-sm text-primary/70"> + <div className="relative flex size-3"> + {/* biome-ignore lint/style/useSelfClosingElements: <explanation> */} + <div + className={cn( + "absolute", + "inline-flex", + "h-full", + "w-full", + "animate-ping", + "rounded-full", + "bg-zinc-500", + "opacity-75", + )} + ></div> + {/* biome-ignore lint/style/useSelfClosingElements: <explanation> */} + <div + className={` + relative inline-flex size-3 rounded-full + bg-zinc-600 + `} + ></div> + </div> + Currently {count} other {userString} online. + </div> + ); +} diff --git a/addons/cluster/temp/liveblocks-visitors-cursors.tsx.txt b/addons/cluster/temp/liveblocks-visitors-cursors.tsx.txt new file mode 100644 index 00000000..1e2b7643 --- /dev/null +++ b/addons/cluster/temp/liveblocks-visitors-cursors.tsx.txt @@ -0,0 +1,152 @@ +/** @see https://github.com/aidenybai/million/blob/main/website/components/live.tsx */ + +import { useEffect } from "react"; + +import { useRouter } from "next/router"; + +import { Cursor } from "~/components/Common/cursor"; +import { + RoomProvider, + useOthers, + useUpdateMyPresence, +} from "~/config/liveblocks.config"; + +const COLORS = [ + "#E57373", + "#9575CD", + "#4FC3F7", + "#81C784", + "#FFF176", + "#FF8A65", + "#F06292", + "#7986CB", +]; + +function useLiveCursors( ) { + const updateMyPresence = useUpdateMyPresence( ); + + useEffect(( ) => { + const scroll = { + x: window.scrollX, + y: window.scrollY, + }; + + let lastPosition: { x: number; y: number } | null = null; + + function transformPosition(cursor: { x: number; y: number }) { + return { + x: cursor.x / window.innerWidth, + y: cursor.y, + }; + } + + function onPointerMove(event: PointerEvent) { + // event.preventDefault(); + const position = { + x: event.pageX, + y: event.pageY, + }; + + lastPosition = position; + updateMyPresence({ + cursor: transformPosition(position), + }); + } + + function onPointerLeave( ) { + lastPosition = null; + updateMyPresence({ + cursor: null, + }); + } + + function onDocumentScroll( ) { + if (lastPosition) { + const offsetX = window.scrollX - scroll.x; + const offsetY = window.scrollY - scroll.y; + const position = { + x: lastPosition.x + offsetX, + y: lastPosition.y + offsetY, + }; + + lastPosition = position; + updateMyPresence({ + cursor: transformPosition(position), + }); + } + + scroll.x = window.scrollX; + scroll.y = window.scrollY; + } + + document.addEventListener("scroll", onDocumentScroll); + document.addEventListener("pointermove", onPointerMove); + document.addEventListener("pointerleave", onPointerLeave); + + return ( ) => { + document.removeEventListener("scroll", onDocumentScroll); + document.removeEventListener("pointermove", onPointerMove); + document.removeEventListener("pointerleave", onPointerLeave); + }; + }, [updateMyPresence]); + + const others = useOthers( ); + + const cursors: { + connectionId: number; + x: number; + y: number; + }[] = []; + + for (const { connectionId, presence } of others) { + if (presence.cursor) { + cursors.push({ + connectionId, + x: presence.cursor.x * window.innerWidth, + y: presence.cursor.y, + }); + } + } + + return cursors; +} + +export function Cursors( ) { + const cursors = useLiveCursors( ); + + return ( + <> + {cursors.map(({ connectionId, x, y }) => + ( + <Cursor + color={COLORS[connectionId % COLORS.length] || ""} + key={connectionId} + x={x} + y={y} + /> + ))} + </> + ); +} + +export function getCount( ) { + const others = useOthers( ); + + return others.length; +} + +export function LiveProvider({ children }: { children: ReactNode }) { + const { pathname } = useRouter( ); + + return ( + <RoomProvider + id={`million-${pathname}`} + initialPresence={{ + cursor: null, + }} + > + {children} + <Cursors /> + </RoomProvider> + ); +} diff --git a/addons/cluster/temp/liveblocks.config.ts.txt b/addons/cluster/temp/liveblocks.config.ts.txt new file mode 100644 index 00000000..5bd19e79 --- /dev/null +++ b/addons/cluster/temp/liveblocks.config.ts.txt @@ -0,0 +1,141 @@ +// import type { JsonObject } from "@liveblocks/client"; + +/** @see https://liveblocks.io */ + +import { createClient } from "@liveblocks/client"; +import { createRoomContext } from "@liveblocks/react"; + +import { env } from "~/env"; + +const client = createClient({ + publicApiKey: env.NEXT_PUBLIC_LIVEBLOCKS_PUBLIC_KEY!, + throttle: 16, + + // authEndpoint: "/api/auth", + // throttle: 100, +}); + +// Presence represents the properties that exist on every user in the Room +// and that will automatically be kept in sync. Accessible through the +// `user.presence` property. Must be JSON-serializable. +// interface Presence extends JsonObject { +// cursor: { x: number; y: number } | null; +// } +type Presence = { + cursor: { x: number; y: number } | null; + + // cursor: { x: number, y: number } | null, + // ... +}; + +// } & JsonObject; + +// Optionally, Storage represents the shared document that persists in the +// Room, even after all users leave. Fields under Storage typically are +// LiveList, LiveMap, LiveObject instances, for which updates are +// automatically persisted and synced to all connected clients. +type Storage = { + // author: LiveObject<{ firstName: string, lastName: string }>, + // ... +}; + +// Optionally, UserMeta represents static/readonly metadata on each user, as +// provided by the own custom auth back end (if used). Useful for data that +// will not change during a session, like a user's name or avatar. +type UserMeta = { + // id?: string, // Accessible through `user.id` + // info?: Json, // Accessible through `user.info` +}; + +// Optionally, the type of custom events broadcast and listened to in this +// room. Use a union for multiple events. Must be JSON-serializable. +type RoomEvent = { + // type: "NOTIFICATION", + // ... +}; + +// Optionally, when using Comments, ThreadMetadata represents metadata on +// each thread. Can only contain booleans, strings, and numbers. +type ThreadMetadata = { + // resolved: boolean; + // quote: string; + // time: number; +}; + +export const { + suspense: { + RoomProvider, + useAddReaction, + useBatch, + useBroadcastEvent, + useCanRedo, + useCanUndo, + useCreateComment, + useCreateThread, + useDeleteComment, + useEditComment, + useEditThreadMetadata, + useErrorListener, + useEventListener, + useHistory, + useList, + useLostConnectionListener, + useMap, + useMutation, + useMyPresence, + useObject, + useOther, + useOthers, + useOthersConnectionIds, + useOthersMapped, + useRedo, + useRemoveReaction, + useRoom, + useSelf, + useStatus, + useStorage, + useThreads, + useUndo, + useUpdateMyPresence, + useUser, + }, +} = createRoomContext<Presence, Storage, UserMeta, RoomEvent, ThreadMetadata>( + client, + { + async resolveMentionSuggestions({ roomId, text }) { + // Used only for Comments. Return a list of userIds that match `text`. + // These userIds are used to create a mention list when typing in the + // composer. For example when you type "@jo", `text` will be `"jo"`, + // and you should to return an array with John and Joanna's userIds: + // ["john@example.com", "joanna@example.com"] + + const userIds = await __fetchAllUserIdsFromDB__(roomId); + + // Return all userIds if no `text` + if (!text) { + return userIds; + } + + // + // Otherwise, filter userIds for the search `text` and return + return userIds.filter((userId) => + userId.toLowerCase().includes(text.toLowerCase()), + ); + }, + + // async resolveUsers({ userIds }) { + async resolveUsers() { + // Used only for Comments. Return a list of user information retrieved + // from `userIds`. This info is used in comments, mentions etc. + + // const usersData = await __fetchUsersFromDB__(userIds); + // + // return usersData.map((userData) => ({ + // name: userData.name, + // avatar: userData.avatar.src, + // })); + + return []; + }, + }, +); diff --git a/addons/cluster/temp/locale-layout.tsx.txt b/addons/cluster/temp/locale-layout.tsx.txt new file mode 100644 index 00000000..3516cda3 --- /dev/null +++ b/addons/cluster/temp/locale-layout.tsx.txt @@ -0,0 +1,90 @@ +import { LocaleLayoutProps } from "~/types"; +import { Metadata, Viewport } from "next"; + +import { polyfillCountryFlagEmojis } from "country-flag-emoji-polyfill"; +import localFont from "next/font/local"; +import { headers } from "next/headers"; +import { notFound } from "next/navigation"; + +import { ShowInfo } from "~/components/Common/indicators-error"; +import LoglibAnalytics from "~/components/Common/loglib-analytics"; +import { TailwindScreens } from "~/components/Common/tailwind-indicator"; +import ZustandProvider from "~/components/Modules/Zustand/ZuProvider"; +import AuthProvider from "~/components/Providers/AuthProvider"; +import { NextThemesProvider } from "~/components/Providers/ThemeProvider"; +import { TooltipProvider } from "~/components/Providers/Tooltip"; +import { locales } from "~/config/navigation"; +import { TRPC } from "~/core/trpc/react"; +import { RootLayoutMetadata } from "~/data/meta"; +import { cn } from "~/utils"; +import { fontSans } from "~/utils/fonts"; + +import "@radix-ui/themes/styles.css"; +import "~/styles/globals.css"; + +// Each page in the app will have the following metadata, you can override +// them by defining the metadata in the page.tsx or in children layout.tsx +export const metadata: Metadata = RootLayoutMetadata; + +// @see https://nextjs.org/docs/app/api-ref/functions/generate-viewport +export const viewport: Viewport = { + themeColor: [ + { + color: "white", + media: "(prefers-color-scheme: light)", + }, + { + color: "black", + media: "(prefers-color-scheme: dark)", + }, + ], +}; + +const twemoji = localFont({ + display: "swap", + src: "../../styles/fonts/twemoji.woff2", + variable: "--font-twemoji", +}); + +// This is the "root" layout. It checks for valid locales, +// sets up the fonts, themes, analytics, providers, & more +// This file serves as the primary entry point for the app +// @see https://github.com/blefnk/relivator-nextjs-template#readme +export default async function LocaleLayout({ + children, + params: { locale }, +}: LocaleLayoutProps) { + if (!locales.includes(locale)) { + return notFound(); + } + + polyfillCountryFlagEmojis(); + + return ( + <html lang={locale} suppressHydrationWarning> + <body + className={cn( + "min-h-screen bg-background font-sans antialiased", + twemoji.variable, + fontSans.variable, + )} + > + <TRPC data={headers()}> + <NextThemesProvider> + <TooltipProvider> + <ZustandProvider> + <AuthProvider> + <ShowInfo /> + {children} + <Toaster /> + </AuthProvider> + </ZustandProvider> + </TooltipProvider> + <TailwindScreens /> + <LoglibAnalytics /> + </NextThemesProvider> + </TRPC> + </body> + </html> + ); +} // =================================================================// ? The content below is deprecated/experimental,// ? and planned to be removed or added in v1.3.0// =================================================================// import { GeistSans } from "geist/font/sans";// const bodyStyle =// locale === "en"// ? cn(// "container min-h-screen bg-background font-sans antialiased",// fontHeading.variable,// GeistSans.variable,// )// : cn(// "container min-h-screen bg-background font-sans antialiased",// fontHeading.variable,// fontSans.variable,// );// =================================================================// <body// className={cn("min-h-screen bg-background font-sans antialiased",// fontHeading.variable, locale === "en" ? GeistSans.variable : fontSans.variable)}// >// =================================================================// declare module "react" {// / eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style,// @typescript-eslint/consistent-type-definitions, @typescript-eslint/naming-convention// interface CSSProps {// Allow CSS custom props// [key: `--${string}`]: number | string;// }// }// =================================================================// import localFont from "next/font/local";// import { unstable_after as after } from "next/server";// import { Flowbite, ThemeModeScript } from "flowbite-react";// import { customTheme } from "~/components/Providers/FlowbiteTheme";// Secondary task after the page is rendered; return is the primary task// Next.js+tRPC | @see https://baselime.io/docs/sending-data/languages/next.js// @see https://nextjs.org/blog/next-15-rc#executing-code-after-a-response-with-nextafter-experimental// after(() => {// if (Math.floor(process.uptime()) > 15) {// console.warn(`LocaleLayout took too long to render (~${Math.floor(process.uptime())}s).`);// } else if (debug) {// console.info(`LocaleLayout has been rendered in ${Math.floor(process.uptime())} seconds.`);// }// });// =================================================================// If you get `Failed to fetch `Roboto` from Google Fonts.`// The most popular reason is just broken internet connection.// You can try to use the local fonts instead of Google Fonts.// @example:// const interSans = localFont({// src: "../../styles/fonts/inter.woff2",// variable: "--font-sans",// display: "swap",// });// const fontHeading = localFont({// src: "../../styles/fonts/inter.woff2",// variable: "--font-heading",// weight: "600",// display: "swap",// });// =================================================================// Uncomment if Flowbite is needed; currently shadcn/ui is used only// Remove Flowbite in the 1.3.0 version of Relivator// Can be added manually by running `reliverse` command// And do not forget to add the Flowbite provider to the layout:// <head><ThemeModeScript /></head>// <Flowbite theme={{ theme: customTheme }}>...</Flowbite>// =================================================================// import { Theme } from "@radix-ui/themes";// <Theme asChild radius="full">// </Theme> diff --git a/addons/cluster/temp/localization-gsap-useeffect-error.txt b/addons/cluster/temp/localization-gsap-useeffect-error.txt new file mode 100644 index 00000000..ea8b3210 --- /dev/null +++ b/addons/cluster/temp/localization-gsap-useeffect-error.txt @@ -0,0 +1,548 @@ +"use client"; + +import { ButtonProps } from "~/components/primitives"; + +import { ChangeEvent, ReactNode } from "react"; +import { useRef, useState, useTransition } from "react"; + +import clsx from "clsx"; +import { useParams, usePathname, useRouter } from "next/navigation"; +import { useLocale } from "next-intl"; + +import { labels, locales } from "~/config/navigation"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "~/components/Primitives/ui/dropdown"; +import { Button } from "~/components/Primitives/ui/Button"; +import { cn } from "~/utils"; + +import "~/styles/flags/flags.min.css"; + + +// TODO Fix: When closing dropdown on production the following error occurs: +// `Application error: a client-side exception has occurred (see the browser console for more information).` +// DOMException: Node.insertBefore: Child to insert before is not a child of this node + +type LocalizationMainSwitcherProps = { + tTitle: string; +} & ButtonProps; + +type Props = { + children: ReactNode; + defaultValue: string; + label: string; +}; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function LocalizationMainSwitcher({ + tTitle, +}: LocalizationMainSwitcherProps) { + const currentLocale = useLocale(); + + return ( + <div> + <DropdownMenu> + <DropdownMenuTrigger asChild className="outline-none"> + <Button variant="outline"> + <span> + <span + className={` + hidden + + md:flex + `} + > + <LocaleFlagIcon locale={currentLocale} /> + {/* <LocaleFlagIcon flag={currentAppLocale.flag} /> */} + {labels[currentLocale as keyof typeof labels]} + {/* {currentAppLocale.label} */} + </span> + <span + className={` + block + + md:hidden + `} + > + <LocaleFlagIcon locale={currentLocale} /> + {/* <LocaleFlagIcon flag={currentAppLocale.flag} /> */} + </span> + </span> + </Button> + </DropdownMenuTrigger> + <DropdownMenuContent> + <DropdownMenuLabel className="font-medium"> + {tTitle} + </DropdownMenuLabel> + <DropdownMenuSeparator /> + <DropdownMenuRadioGroup + value={currentLocale} + + // onValueChange={(val) => handleClick(val)} + // onValueChange={handleClick} + > + {locales.map((locale: string) => ( + <DropdownMenuRadioItem + className="flex" + key={locale} + value={locale} + + // value={locale.code} + // key={locale.code} + > + {/* <LocaleFlagIcon flag={locale.flag} /> */} + {/* ========================================== */} + {/* TODO: fix flags doesn't displaying in dropdown */} + {/* <LocaleFlagIcon locale={locale} /> */} + {/* ========================================== */} + {labels[locale as keyof typeof labels]} + {/* {locale.label} */} + </DropdownMenuRadioItem> + ))} + </DropdownMenuRadioGroup> + </DropdownMenuContent> + </DropdownMenu> + </div> + ); +} + +/** @see https://github.com/blefnk/relivator-nextjs-template/pull/3/commits */ + +type LocaleFlagIconProps = { + locale: string; +}; + +/* export function LocaleFlagIconNew({ locale }: LocaleFlagIconProps) { + const baseLocale = locale.split("-")[0]; + const classNameMap = { + en: "fi fi-gb", + uk: "fi fi-ua", + pl: "fi fi-pl border border-b-0 border-zinc-200 dark:border-none", + hi: "fi fi-in", + fa: "fi fi-ir", + zh: "fi fi-cn", + }; + // } as Record<string, string>; + const className = classNameMap[baseLocale] || `fi fi-${baseLocale}`; + + return <span aria-hidden="true" className={className} />; +} */ + +// function LocaleFlagIcon({ flag }: { flag: string }) { + // biome-ignore lint/style/useSelfClosingElements: <explanation> +// return <span aria-hidden="true" className={`mr-2 ${flag}`}></span>; +// } + +export function LanguageSwitcherSelect({ + children, + defaultValue, + label, +}: Props) { + const router = useRouter(); + const [isPending, startTransition] = useTransition(); + const pathname = usePathname(); + const parameters = useParams(); + + function onSelectChange(event: ChangeEvent<HTMLSelectElement>) { + const nextLocale = event.target.value; + + startTransition(() => { + router.replace( + // @ts-expect-error -- TypeScript will validate that only known `params` + // are used in combination with a given `pathname`. Since the two will + // always match for the current route, we can skip runtime checks. + { + params: parameters, + pathname, + }, + { + locale: nextLocale, + }, + ); + }); + } + + return ( + <label + className={clsx( + "relative text-gray-400", + isPending && + ` + transition-opacity + + [&:disabled]:opacity-30 + `, + )} + > + <p className="sr-only">{label}</p> + <select + className="inline-flex appearance-none bg-transparent py-3 pl-2 pr-6" + defaultValue={defaultValue} + disabled={isPending} + onChange={onSelectChange} + > + {children} + </select> + <span className="pointer-events-none absolute right-2 top-[8px]">⌄</span> + </label> + ); +} + +// type LocaleSwitcherProps = { +// tTitle: string; +// translateLanguageNames: boolean; +// }; + +// TODO: Move mostly everything from LocalizationMainSwitcher to LocaleSwitcher +// export function LocaleSwitcher({ tTitle, translateLanguageNames }: LocaleSwitcherProps) { +export function LocaleSwitcher() { + // const searchParams = useSearchParams(); + const pathname = usePathname(); + const router = useRouter(); + + // const mounted = useIsClient(); + const currentLocale = useLocale(); + const [isOptionsExpanded, setIsOptionsExpanded] = useState(false); + const containerRef = useRef<HTMLDivElement | null>(null); + const optionsContainerRef = useRef<HTMLDivElement | null>(null); + + // const arrowRef = useRef<SVGSVGElement | null>(null); // doesn't work + + // gsap.registerPlugin(useGSAP); // 🐞 + + // TODO: Find the current app locale object or default to the first one + // const currentAppLocale = + // locales.find((locale) => locale.code === currentLocale) || + // locales.find((locale) => locale.code === defaultLocale); + + // TODO: In case locales array is empty + // if (!currentAppLocale) { + // consola.error("currentAppLocale is undefined"); + // return null; + // } + + // if (!mounted) { + // return ( + // <Button + // disabled + // aria-label="Language Switcher" + // className="rounded-lg border" + // variant="ghost" + // {...props} + // > + // <span className="hidden sm:block"> + // <LocaleFlagIcon locale={currentLocale} /> + // {/* <LocaleFlagIcon flag={currentAppLocale.flag} /> */} + // {labels[currentLocale as keyof typeof labels]} + // {/* {currentAppLocale.label} */} + // </span> + // <span className="-mr-2 block sm:hidden"> + // <LocaleFlagIcon locale={currentLocale} /> + // {/* <LocaleFlagIcon flag={currentAppLocale.flag} /> */} + // </span> + // </Button> + // ); + // } + + type Option = { + code: string; + country: string; + }; + + const options = locales.map((locale) => ({ + code: locale, + country: labels[locale], + })) satisfies Option[]; + + const setOption = (option: Option) => { + setIsOptionsExpanded(false); + + const pathSegments = (pathname || "") + .split("/") + .filter((segment) => segment.trim() !== ""); + let newPath = `/${option.code}`; + + if ( + pathSegments.length > 0 && + options.some((opt) => opt.code === pathSegments[0]) + ) { + pathSegments[0] = option.code; + newPath = `/${pathSegments.join("/")}`; + } else { + newPath += pathname !== "/" ? pathname : ""; + } + + router.push(newPath); + }; + + // const handleClick = (locale: string) => { + // router.replace( + // `${pathname}${locale ? `/${locale}` : ""}${ + // searchParams ? `/?${searchParams}` : "" + // }`, + // { scroll: true }, + // ); + // router.replace(`${pathname}?${searchParams}`, { locale }); + // router.replace(`${pathname}${locale ? `/${locale}` : `${pathname}`}`, { + // scroll: true, + // }); + // router.replace(`${pathname}?${searchParams}`, { + // scroll: true, + // }); + // redirect(`${pathname}?${searchParams}`); + // }; + + // Ensure GSAP animation works on the first interaction by setting + // the initial styles (they will be in the DOM but hidden). + // upd. Since CSS will handle the initial hidden state, + // we can remove the GSAP set method in the useEffect. + // TODO: Remove when we're sure it's no longer useful. + /* useEffect(() => { + gsap.set(optionsContainerRef.current, { + display: "none", + opacity: 0, + scaleY: 0, + transformOrigin: "top", + }); + gsap.set(arrowRef.current, { // doesn't work + rotate: 0, + }); + }, []); */ + + /* useGSAP( // 🐞 + () => { + if (isOptionsExpanded) { + gsap.to(optionsContainerRef.current, { + display: "block", + duration: 0.5, + ease: "power2.inOut", + opacity: 1, + scaleY: 1, + }); + + // gsap.to(arrowRef.current, { + // rotate: 180, + // duration: 0.5, + // ease: "expo.inOut", + // }); // doesn't work + } else { + gsap.to(optionsContainerRef.current, { + duration: 0.5, + ease: "power2.inOut", + onComplete: () => { + if (optionsContainerRef.current) { + optionsContainerRef.current.style.display = "none"; + } + }, + opacity: 0, + scaleY: 0, + }); + + // gsap.to(arrowRef.current, { + // rotate: 0, + // duration: 0.5, + // ease: "power2.inOut", + // }); // doesn't work + } + }, + { + dependencies: [isOptionsExpanded], + scope: containerRef, + }, + ); */ // 🐞 + + // TODO: Fix dropdown items displaying + return ( + <div className="flex items-center justify-center"> + {/* <DropdownMenu> */} + <div className="relative text-sm" ref={containerRef}> + <div> + {/* <DropdownMenuTrigger asChild className="outline-none"> */} + <Button + className={` + inline-flex items-center justify-between rounded-lg px-6 py-2.5 + text-center text-sm font-medium + `} + onBlur={() => setIsOptionsExpanded(false)} + onClick={() => setIsOptionsExpanded(!isOptionsExpanded)} + variant="outline" + > + <span className="flex"> + <LocaleFlagIcon locale={currentLocale} /> + <span + className={` + hidden + + md:block + `} + > +  {labels[currentLocale as keyof typeof labels]} + </span> + </span> + <svg + className={` + size-4 transition-transform duration-200 + + ease-in-out${isOptionsExpanded ? "rotate-180" : "rotate-0"} + `} + fill="none" + stroke="currentColor" + viewBox="0 0 24 24" + + // TODO: GSAP: + // className="h-4 w-4 transform transition-transform duration-200 ease-in-out" // doesn't work + > + <title>Arrow + + + + + {/* */} + {/* */} + {/* {tTitle} */} + {/*

{tTitle}

*/} + {/* */} + {/* +
    + {options.map((locale, index) => ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions +
  • setIsOptionsExpanded(false)} + onMouseDown={ + (event_) => { + event_.preventDefault(); + setOption(locale); + } + + // + +   {locale.country} + {pathname === `/${locale.code}` && ( + + Locale Switcher Arrow Icon + + + )} + {/* */} +
  • + ))} +
+ + {/*
*/} + {/*
*/} + + {/* */} + + ); +} + +function LocaleFlagIcon({ locale }: LocaleFlagIconProps) { + // const baseLocale = locale.split("-")[0]; + + const flagIcon = locale; + + if (flagIcon === "en") { + return