diff --git a/.cta.json b/.cta.json deleted file mode 100644 index d96f1b7..0000000 --- a/.cta.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "projectName": "linearmouse-website", - "mode": "file-router", - "typescript": true, - "tailwind": true, - "packageManager": "pnpm", - "git": false, - "install": true, - "addOnOptions": {}, - "includeExamples": true, - "envVarValues": {}, - "routerOnly": false, - "version": 1, - "framework": "react-cra", - "chosenAddOns": [ - "cloudflare" - ] -} \ No newline at end of file diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c550055 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +Dockerfile +.dockerignore +node_modules +npm-debug.log +README.md +.next +.git diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100755 index 0000000..f0f3abe --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,6 @@ +{ + "extends": "next/core-web-vitals", + "rules": { + "@next/next/no-img-element": "off" + } +} diff --git a/.github/workflows/create-and-publish-docker-image.yml b/.github/workflows/create-and-publish-docker-image.yml new file mode 100644 index 0000000..27df2b6 --- /dev/null +++ b/.github/workflows/create-and-publish-docker-image.yml @@ -0,0 +1,49 @@ +name: Create and publish Docker image + +on: + push: + branches: + - main + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Log in to the Container registry + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index c150577..737d872 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,35 @@ -node_modules +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc .DS_Store -dist -dist-ssr -*.local -.env -.nitro -.tanstack -.wrangler -.output -.vinxi -__unconfig* -todos.json - -.dev.vars* -!.dev.vars.example -!.env.example +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..9f5de3d --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +.next +.swc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..ef2a84e --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "printWidth": 120, + "singleQuote": true, + "semi": false, + "trailingComma": "none", + "importOrder": ["", "^components/", "^./"], + "importOrderSeparation": true +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 00b5278..ad92582 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,11 +1,3 @@ { - "files.watcherExclude": { - "**/routeTree.gen.ts": true - }, - "search.exclude": { - "**/routeTree.gen.ts": true - }, - "files.readonlyInclude": { - "**/routeTree.gen.ts": true - } + "editor.formatOnSave": true } diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..50a7455 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,48 @@ +FROM node:24-slim AS deps +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable +WORKDIR /app + +COPY pnpm-lock.yaml package.json ./ +RUN pnpm install --frozen-lockfile + +FROM node:24-slim AS builder +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN pnpm run build + +FROM node:24-slim AS runner +WORKDIR /app + +RUN apt update && apt install -y wget + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder /app/public ./public + +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +ENV HOSTNAME="0.0.0.0" +ENV PORT=3000 + +HEALTHCHECK --timeout=3s --start-period=10s \ + CMD wget -nv -t1 --spider http://localhost:3000/ || exit 1 + +CMD ["node", "server.js"] diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 4aaad03..ecee56a --- a/README.md +++ b/README.md @@ -1,193 +1,5 @@ -Welcome to your new TanStack Start app! +# [linearmouse.app](https://linearmouse.app) -# Getting Started +The LinearMouse website. -To run this application: - -```bash -pnpm install -pnpm dev -``` - -# Building For Production - -To build this application for production: - -```bash -pnpm build -``` - -## Testing - -This project uses [Vitest](https://vitest.dev/) for testing. You can run the tests with: - -```bash -pnpm test -``` - -## Styling - -This project uses [Tailwind CSS](https://tailwindcss.com/) for styling. - -### Removing Tailwind CSS - -If you prefer not to use Tailwind CSS: - -1. Remove the demo pages in `src/routes/demo/` -2. Replace the Tailwind import in `src/styles.css` with your own styles -3. Remove `tailwindcss()` from the plugins array in `vite.config.ts` -4. Uninstall the packages: `pnpm add @tailwindcss/vite tailwindcss --dev` - - - -## Routing - -This project uses [TanStack Router](https://tanstack.com/router) with file-based routing. Routes are managed as files in `src/routes`. - -### Adding A Route - -To add a new route to your application just add a new file in the `./src/routes` directory. - -TanStack will automatically generate the content of the route file for you. - -Now that you have two routes you can use a `Link` component to navigate between them. - -### Adding Links - -To use SPA (Single Page Application) navigation you will need to import the `Link` component from `@tanstack/react-router`. - -```tsx -import { Link } from "@tanstack/react-router"; -``` - -Then anywhere in your JSX you can use it like so: - -```tsx -About -``` - -This will create a link that will navigate to the `/about` route. - -More information on the `Link` component can be found in the [Link documentation](https://tanstack.com/router/v1/docs/framework/react/api/router/linkComponent). - -### Using A Layout - -In the File Based Routing setup the layout is located in `src/routes/__root.tsx`. Anything you add to the root route will appear in all the routes. The route content will appear in the JSX where you render `{children}` in the `shellComponent`. - -Here is an example layout that includes a header: - -```tsx -import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router' - -export const Route = createRootRoute({ - head: () => ({ - meta: [ - { charSet: 'utf-8' }, - { name: 'viewport', content: 'width=device-width, initial-scale=1' }, - { title: 'My App' }, - ], - }), - shellComponent: ({ children }) => ( - - - - - -
- -
- {children} - - - - ), -}) -``` - -More information on layouts can be found in the [Layouts documentation](https://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts). - -## Server Functions - -TanStack Start provides server functions that allow you to write server-side code that seamlessly integrates with your client components. - -```tsx -import { createServerFn } from '@tanstack/react-start' - -const getServerTime = createServerFn({ - method: 'GET', -}).handler(async () => { - return new Date().toISOString() -}) - -// Use in a component -function MyComponent() { - const [time, setTime] = useState('') - - useEffect(() => { - getServerTime().then(setTime) - }, []) - - return
Server time: {time}
-} -``` - -## API Routes - -You can create API routes by using the `server` property in your route definitions: - -```tsx -import { createFileRoute } from '@tanstack/react-router' -import { json } from '@tanstack/react-start' - -export const Route = createFileRoute('/api/hello')({ - server: { - handlers: { - GET: () => json({ message: 'Hello, World!' }), - }, - }, -}) -``` - -## Data Fetching - -There are multiple ways to fetch data in your application. You can use TanStack Query to fetch data from a server. But you can also use the `loader` functionality built into TanStack Router to load the data for a route before it's rendered. - -For example: - -```tsx -import { createFileRoute } from '@tanstack/react-router' - -export const Route = createFileRoute('/people')({ - loader: async () => { - const response = await fetch('https://swapi.dev/api/people') - return response.json() - }, - component: PeopleComponent, -}) - -function PeopleComponent() { - const data = Route.useLoaderData() - return ( -
    - {data.results.map((person) => ( -
  • {person.name}
  • - ))} -
- ) -} -``` - -Loaders simplify your data fetching logic dramatically. Check out more information in the [Loader documentation](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#loader-parameters). - -# Demo files - -Files prefixed with `demo` can be safely deleted. They are there to provide a starting point for you to play around with the features you've installed. - -# Learn More - -You can learn more about all of the offerings from TanStack in the [TanStack documentation](https://tanstack.com). - -For TanStack Start specific documentation, visit [TanStack Start](https://tanstack.com/start). +[Help translate](https://crowdin.com/project/linearmouse) diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx new file mode 100644 index 0000000..a030edd --- /dev/null +++ b/app/[locale]/layout.tsx @@ -0,0 +1,42 @@ +import GlobalStyle from 'components/GlobalStyle' +import StyledComponentsRegistry from 'components/styled-components-regsitry' +import { NextIntlClientProvider } from 'next-intl' +import { hasLocale } from 'next-intl' +import { setRequestLocale } from 'next-intl/server' +import { notFound } from 'next/navigation' + +import { routing } from 'i18n/routing' + +export function generateStaticParams() { + return routing.locales.map((locale) => ({ locale })) +} + +export default async function Layout({ + children, + params +}: { + children: React.ReactNode + params: Promise<{ locale: string }> +}) { + const { locale } = await params + if (!hasLocale(routing.locales, locale)) { + notFound() + } + setRequestLocale(locale) + + return ( + + + + + + + + + + {children} + + + + ) +} diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx new file mode 100755 index 0000000..1186fa6 --- /dev/null +++ b/app/[locale]/page.tsx @@ -0,0 +1,33 @@ +import { getTranslations, setRequestLocale } from 'next-intl/server' + +import Features from 'components/features' +import Footer from 'components/footer' +import Header from 'components/header' +import Hero from 'components/hero' + +export const generateMetadata = async ({ params }: { params: Promise<{ locale: string }> }) => { + const { locale } = await params + const t = await getTranslations({ locale, namespace: 'index' }) + + return { + title: t('title'), + description: t('description') + } +} + +export default async function Home({ params }: { params: Promise<{ locale: string }> }) { + const { locale } = await params + setRequestLocale(locale) + + return ( + <> +
+ + + + + +