Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
domenkoscak committed Nov 19, 2024
0 parents commit 2b3a146
Show file tree
Hide file tree
Showing 31 changed files with 7,868 additions and 0 deletions.
106 changes: 106 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/**
* This is intended to be a basic starting point for linting in your app.
* It relies on recommended configs out of the box for simplicity, but you can
* and should modify this configuration to best suit your team's needs.
*/

/** @type {import('eslint').Linter.Config} */
module.exports = {
root: true,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
env: {
browser: true,
commonjs: true,
es6: true,
},
ignorePatterns: ['!**/.server', '!**/.client'],

// Base config
extends: ['eslint:recommended'],

overrides: [
// React
{
files: ['**/*.{js,jsx,ts,tsx}'],
plugins: ['react', 'jsx-a11y'],
extends: [
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:react-hooks/recommended',
'plugin:jsx-a11y/recommended',
],
settings: {
react: {
version: 'detect',
},
formComponents: ['Form'],
linkComponents: [
{ name: 'Link', linkAttribute: 'to' },
{ name: 'NavLink', linkAttribute: 'to' },
],
'import/resolver': {
typescript: {},
},
},
},

// Typescript
{
files: ['**/*.{ts,tsx}'],
plugins: ['@typescript-eslint', 'import'],
parser: '@typescript-eslint/parser',
settings: {
'import/internal-regex': '^~/',
'import/resolver': {
node: {
extensions: ['.ts', '.tsx'],
},
typescript: {
alwaysTryTypes: true,
project: ['./frontend/tsconfig.json'],
},
},
},
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:import/recommended',
'plugin:import/typescript',
],
},

// Node
{
files: ['.eslintrc.cjs'],
env: {
node: true,
},
},
{
files: ['app/i18n/**/*', 'app/root.tsx'], // Exclude specific files
rules: {
'no-restricted-imports': 'off', // Disable the rule for these files
},
},
],
rules: {
'no-restricted-imports': [
'error',
{
paths: [
{
name: 'react-i18next',
importNames: ['useTranslation', 'Trans'],
message:
'Use the custom typesafe `useTranslation` and `Trans` from `~/i18n/i18n` instead.',
},
],
},
],
},
};
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules

/.cache
/build
.env

.DS_Store
5 changes: 5 additions & 0 deletions .infisical.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"workspaceId": "4de503f3-f5e7-40fb-8175-0c498f1694ba",
"defaultEnvironment": "",
"gitBranchToEnvironmentMapping": null
}
45 changes: 45 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Ignore artifacts:
build
coverage

# dependencies
/node_modules
/.pnp
.pnp.js
pnpm-lock.yaml

# testing
/coverage

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

# husky
/.husky/

# Ignore autogenerated files
.infisical.json
components.json

# Handlebars templates
*.hbs
18 changes: 18 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"quoteProps": "as-needed",
"jsxSingleQuote": false,
"trailingComma": "all",
"bracketSpacing": true,
"bracketSameLine": true,
"arrowParens": "always",
"endOfLine": "lf",
"tailwindConfig": "./tailwind.config.ts",
"tailwindAttributes": ["className"],
"tailwindFunctions": ["cn", "tw", "clsx", "cva"],
"plugins": ["prettier-plugin-organize-imports", "prettier-plugin-tailwindcss"]
}
95 changes: 95 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Remix Template with TailwindCSS, Shadcn, Zodios, i18next, and More

This repository provides a ready-to-use template for creating a Remix application with modern tooling, including TailwindCSS, Shadcn, environment variable validation with T3-oss, Zodios API client, i18next for localization, and more.

## 🚀 Quick Start

To create a new Remix project from this template, run the following command:

```bash
npx create-remix@latest --template <your-github-username>/<repo-name>
```

This will set up a new Remix app with all the configurations and dependencies outlined below.

### Running the App

After setting up your project, navigate to the project folder and run the following command to install the dependencies:

```bash
pnpm install
```

To start the development server:

```bash
pnpm dev
```

This will run the app locally. Open [http://localhost:3000](http://localhost:3000) in your browser to view the app.

## 🛠️ What's Included

This template comes with the following features:

- **Remix**: Uses Remix v1 routing with file-based routing.
- **TailwindCSS**: A utility-first CSS framework for creating responsive, modern designs.
- **Shadcn**: A component library providing pre-designed and customizable components.
- **T3-Oss Env Validation**: T3-oss environment variable validation for managing your app's environment variables securely.
- **Zodios**: An API client based on Zod to create and validate API requests seamlessly.
- **i18next**: Localization framework for adding multi-language support to your app.
- **Infisical**: Secure secret management for your app, storing API keys, DB credentials, and other sensitive information.
- **Prettier**: Code formatting for consistent style across your project.
- **ESLint**: Static analysis tool for identifying problematic patterns in JavaScript/TypeScript code.
- **VSCode Settings**: Pre-configured VSCode settings for consistent development experience.

## 📁 Special Folder and Filename Conventions

In this project, files and folders with a double underscore (`__`) in their filename or folder name are treated as special. Specifically:

- A file named `__guarded.tsx` in the `__guarded` folder is used as the layout for all routes within that folder. This is a key feature of the routing convention used in this template. This approach helps in grouping routes that share common layouts or behavior.

Example:

- `__guarded/__guarded.tsx` will act as a layout for every page inside the `__guarded` folder.

## 🗂️ Folder Structure Example

```
src/
├── routes/
│ ├── __guarded/
│ │ ├── index.tsx
│ │ └── about.tsx
│ └── __guarded.tsx
├── tailwind.css
└── ...
```

In this case, the `__guarded.tsx` layout will be applied to `index.tsx`, `about.tsx`, or any other route placed inside the `__guarded` folder.

## ⚙️ Additional Setup

- **VSCode Settings**: Make sure to install the recommended VSCode extensions to ensure proper formatting and linting.
- **Infisical**:

Infisical is used to securely manage your environment variables, including API keys, database credentials, and other sensitive information. To initialize Infisical:

1. **Create an Infisical Account**:
- Go to [Infisical](https://app.infisical.com) and sign up for an account.
2. **Create Infisical Project**:
- You can go to [Infisical Dashboard](https://app.infisical.com) and create a new project. There you can add your environment variables, API keys, and other sensitive information.
3. **Login to Infisical**:

- Run the following command to log in to your Infisical account:
```bash
infisical login
```

4. **Initialize Your Project**:
- Once logged in, link your project to Infisical by running:
```bash
infisical init
```

By following these steps, you can manage your sensitive environment variables securely in Infisical.
56 changes: 56 additions & 0 deletions app/components/ui/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "~/lib/utils"

const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)

export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"

export { Button, buttonVariants }
50 changes: 50 additions & 0 deletions app/entry.client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { RemixBrowser } from '@remix-run/react';
import i18next from 'i18next';
import I18nextBrowserLanguageDetector from 'i18next-browser-languagedetector';
import Fetch from 'i18next-fetch-backend';
import { startTransition, StrictMode } from 'react';
import { hydrateRoot } from 'react-dom/client';
import { I18nextProvider, initReactI18next } from 'react-i18next';
import { getInitialNamespaces } from 'remix-i18next/client';
import { defaultNS, fallbackLng, supportedLngs } from './i18n/i18n';

async function main() {
// eslint-disable-next-line import/no-named-as-default-member
await i18next
.use(initReactI18next) // Tell i18next to use the react-i18next plugin
.use(Fetch) // Tell i18next to use the Fetch backend
.use(I18nextBrowserLanguageDetector) // Setup a client-side language detector
.init({
defaultNS,
fallbackLng,
supportedLngs,
ns: getInitialNamespaces(),
detection: {
// Here only enable htmlTag detection, we'll detect the language only
// server-side with remix-i18next, by using the `<html lang>` attribute
// we can communicate to the client the language detected server-side
order: ['htmlTag'],
// Because we only use htmlTag, there's no reason to cache the language
// on the browser, so we disable it
caches: [],
},
backend: {
// We will configure the backend to fetch the translations from the
// resource route /api/locales and pass the lng and ns as search params
loadPath: '/api/locales?lng={{lng}}&ns={{ns}}',
},
});

startTransition(() => {
hydrateRoot(
document,
<I18nextProvider i18n={i18next}>
<StrictMode>
<RemixBrowser />
</StrictMode>
</I18nextProvider>,
);
});
}

main().catch((error) => console.error(error));
Loading

0 comments on commit 2b3a146

Please sign in to comment.