Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
04e5d23
feat: internationalization fullstack setup
micksabox Dec 29, 2024
2a673b3
chore: internationalization configuration
micksabox Dec 29, 2024
2786767
chore: configure settings
micksabox Dec 29, 2024
5f328ed
Merge branch 'feat/translation' into dev
micksabox Dec 29, 2024
f1c85cf
fix: server start and stream
micksabox Dec 29, 2024
4187312
feat: forward client env to browser context
micksabox Dec 29, 2024
bbc3ec2
fix: vite dev server issues by moving /src into /app and setup tsconf…
micksabox Dec 29, 2024
ad319a0
feat: language switcher in footer
micksabox Dec 29, 2024
1473f35
fix: type resolution and parse error for resource.locales
micksabox Dec 30, 2024
ebab202
feat: configure languine.ai
micksabox Dec 30, 2024
c95d07f
feat: footer, header and french language localization
micksabox Dec 30, 2024
e1262f8
fix: tailwind and try transmart
micksabox Dec 30, 2024
be393c6
feat: language select persistence & detection via cookie
micksabox Dec 30, 2024
40d1d26
fix: default locale detection
micksabox Dec 30, 2024
fa4e331
chore: remove unused individual empiricism post
micksabox Jan 2, 2025
670d442
chore: more localization config w/ languine.ai
micksabox Jan 2, 2025
42f63a3
fix: metagame post
micksabox Jan 2, 2025
3455b3a
fix: order of locale detection
micksabox Jan 2, 2025
3f1de81
chore: remove transmart
micksabox Jan 16, 2025
7ced2ca
docs: localization readme
micksabox Jan 16, 2025
5377a75
refactor: translation keys and homepage translated
micksabox Jan 16, 2025
49904ac
feat: more homepage translations keyed
micksabox Jan 16, 2025
82e7850
chore: more translations throughout app
micksabox Jan 18, 2025
b2a8522
chore: localize date picker
micksabox Jan 18, 2025
1ccee57
feat: memery translation
micksabox Jan 28, 2025
42ff3a8
feat: update privacy translations
micksabox Jan 28, 2025
9b8faf6
chore: privacy localized
micksabox Jan 28, 2025
583a556
feat: privacy translations
micksabox Jan 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions .cursorrules
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
You are an expert in React Router 7, TailwindCSS, and TypeScript, focusing on scalable web development.

**Key Principles**

- Provide clear, precise React Router 7 and TypeScript examples.
- Apply immutability and pure functions where applicable.
- Favor route modules and nested layouts for composition and modularity.
- Use meaningful variable names (e.g., `isAuthenticated`, `userRole`).
- Always use kebab-case for file names (e.g., `user-profile.tsx`).
- Prefer named exports for loaders, actions, and components.

**TypeScript & Remix**

- Define data structures with interfaces for type safety.
- Avoid the `any` type, fully utilize TypeScript's type system.
- Organize files: imports, loaders/actions, component logic.
- Use template strings for multi-line literals.
- Utilize optional chaining and nullish coalescing.
- Use nested layouts and dynamic routes where applicable.
- Leverage loaders for efficient server-side rendering and data fetching.
- Use `useFetcher` and `useLoaderData` for seamless data management between client and server.

**File Naming Conventions**

- `*.tsx` for React components
- `*.ts` for utilities, types, and configurations
- `root.tsx` for the root layout
- All files use kebab-case.

**Code Style**

- Use single quotes for string literals.
- Indent with 2 spaces.
- Ensure clean code with no trailing whitespace.
- Use `const` for immutable variables.
- Use template strings for string interpolation.

**Remix-Specific Guidelines**

- Routes are mapped from URL schemas to files in `app/routes.ts`
- Use `<Link>` for navigation, avoiding full page reloads.
- Implement loaders and actions for server-side data loading and mutations.
- Import type `Route` from `./+types/{route-filename}.ts` to type loaders and actions.
- Ensure accessibility with semantic HTML and ARIA labels.
- Leverage route-based loading, error boundaries, and catch boundaries.
- Use the `useFetcher` hook for non-blocking data updates.
- Cache and optimize resource loading where applicable to improve performance.

**Import Order**

1. React Router 7 core modules
2. React and other core libraries
3. Third-party packages
4. Application-specific imports
5. Environment-specific imports
6. Relative path imports

**Error Handling and Validation**

- Implement error boundaries for catching unexpected errors.
- Use custom error handling within loaders and actions.
- Validate user input on both client and server using formData or JSON.

**Testing**

- Use `@testing-library/react` for component testing.
- Write tests for loaders and actions ensuring data correctness.
- Mock fetch requests and responses where applicable.

**Performance Optimization**

- Prefetch routes using `<Link prefetch="intent">` for faster navigation.
- Defer non-essential JavaScript using `<Scripts defer />`.
- Optimize nested layouts to minimize re-rendering.
- Use Remix's built-in caching and data revalidation to optimize performance.

**Security**

- Prevent XSS by sanitizing user-generated content.
- Use Remix's CSRF protection for form submissions.
- Handle sensitive data on the server, never expose in client code.

**Key Conventions**

- Use React Router 7's loaders and actions to handle server-side logic.
- Focus on reusability and modularity across routes and components.
- Follow Remix’s best practices for file structure and data fetching.
- Optimize for performance and accessibility.

**Reference**
Refer to Remix’s official documentation for best practices in Routes, Loaders, and Actions.
4 changes: 3 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
"parser": "@typescript-eslint/parser",
"settings": {
"import/resolver": {
"typescript": true
"typescript": {
"project": "./tsconfig.json"
}
}
},
"plugins": ["unused-imports", "react", "@typescript-eslint", "import", "prettier"],
Expand Down
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# npm run typecheck
npx lint-staged
# npx lint-staged
6 changes: 4 additions & 2 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"streetsidesoftware.code-spell-checker",
"bradlc.vscode-tailwindcss",
"eamodio.gitlens",
"esbenp.prettier-vscode"
"esbenp.prettier-vscode",
"Lokalise.i18n-ally",
"inlang.vs-code-extension"
]
}
}
3 changes: 3 additions & 0 deletions .vscode/i18n-ally-reviews.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Review comments generated by i18n-ally. Please commit this file.

reviews:
8 changes: 7 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,11 @@
"className",
"ngClass",
"tw"
]
],
"i18n-ally.localesPaths": [
"app/i18n/locales",
],
"i18n-ally.sourceLanguage": "en",
"i18n-ally.annotations": true,
"i18n-ally.keystyle": "nested"
}
File renamed without changes
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
'use client'

import * as React from 'react'
import { format } from 'date-fns'
import { intlFormat } from 'date-fns'
import { Calendar as CalendarIcon } from 'lucide-react'

import { cn } from 'src/lib/utils'
import { Button } from 'src/components/ui/button'
import { Calendar } from 'src/components/ui/calendar'
import { Popover, PopoverContent, PopoverTrigger } from 'src/components/ui/popover'
import { useLocale } from 'remix-i18next/react'
import { useTranslation } from 'react-i18next'

type DatePickerProps = {
currentDate?: Date
Expand All @@ -23,6 +25,8 @@ export const DatePicker: React.FC<DatePickerProps> = (props) => {
setDate(props.currentDate)
}, [props.currentDate])

const locale = useLocale('lang')
const { t } = useTranslation()
return (
<Popover>
<PopoverTrigger asChild>
Expand All @@ -31,7 +35,11 @@ export const DatePicker: React.FC<DatePickerProps> = (props) => {
className={cn('w-[280px] justify-start text-left font-normal', !date && 'text-muted-foreground')}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{date ? format(date, 'PPP') : <span>Pick a date</span>}
{date ? (
intlFormat(date, { month: 'long', day: 'numeric', year: 'numeric' }, { locale: locale })
) : (
<span>{t('tracker.pick_date')}</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
Expand Down
47 changes: 47 additions & 0 deletions app/components/footer/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Link } from 'react-router'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incorrect import path for react-router.

The import path should be @remix-run/react instead of react-router.

-import { Link } from 'react-router'
+import { Link } from '@remix-run/react'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { Link } from 'react-router'
import { Link } from '@remix-run/react'

import CognactiveIcon from 'src/assets/icons/cognactive-icon'
import { ArrowUpRightSquare, Github } from 'lucide-react'
import { GITHUB_REPO_BASE, TELEGRAM_CHAT_LINK } from 'src/constants'
import { Button } from '../ui/button'
import { LanguageSelector } from '../language-selector'
import { useTranslation } from 'react-i18next'

const Footer: React.FC = () => {
const { t } = useTranslation()

return (
<footer className="w-full bg-foreground text-xs text-slate-500">
<div className="container p-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<span className="inline-block w-16">
<CognactiveIcon className="fill-slate-500 transition-colors hover:fill-white" darkMode />
</span>
<Button size={'sm'} asChild>
<Link to={TELEGRAM_CHAT_LINK}>
{t('footer.chat-nac-link-title')} <ArrowUpRightSquare className="ml-2 inline-block w-4" />
</Link>
</Button>
<Button size={'sm'} asChild>
<a href={GITHUB_REPO_BASE} target="_blank" rel="noreferrer">
{t('homepage.github-link-title')} &nbsp;
<Github className="w-4" />
</a>
</Button>
</div>
<LanguageSelector triggerClassName="text-white" />
</div>
<p className="my-2">
<Link className="underline underline-offset-auto" to={'/privacy'}>
{t('footer.data-usage-title')}
</Link>
</p>
<p className="bottom-inset">
<span className="font-semibold">{t('footer.medical-disclaimer')}:</span> {t('footer.medical-disclaimer-desc')}
</p>
</div>
</footer>
)
}

export default Footer
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import React from 'react'

import CognactiveIcon from 'src/assets/icons/cognactive-icon'
import { cn } from 'src/lib/utils'
import { Button } from '../ui/button'
import { Github } from 'lucide-react'
import { Link, useNavigation, useMatches } from 'react-router'
import { useSpinDelay } from 'spin-delay'
import { useTranslation } from 'react-i18next'
// import { LanguageSelector } from '../language-selector'
interface IProps {
className?: string
}
export function Header(props: IProps) {
const matches = useMatches()

const isHomepage = matches.findIndex((m) => m.pathname === '/') == matches.length - 1
const isHomepage = matches.every((m) => m.pathname === '/')
const transition = useNavigation()
const busy = transition.state !== 'idle'

Expand All @@ -21,6 +21,8 @@ export function Header(props: IProps) {
minDuration: 300,
})

const { t } = useTranslation()

return (
<div
className={cn(
Expand All @@ -36,11 +38,10 @@ export function Header(props: IProps) {
cognactive
</Link>
<div className="flex items-center gap-4">
{/* <LanguageSelector /> */}
{isHomepage && (
<Button asChild>
<a href="https://github.com/micksabox/cognactive" target="_blank" rel="noreferrer">
View Code &nbsp;
{t('homepage.github-link-title')} &nbsp;
<Github className="w-4" />
</a>
</Button>
Expand Down
43 changes: 22 additions & 21 deletions src/components/hero/index.tsx → app/components/hero/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import {
LayoutGridIcon,
Scan,
MonitorPlay,
FlameIcon,
} from 'lucide-react'
import CognactiveIcon from 'src/assets/icons/cognactive-icon'

import { Button } from '../ui/button'
import { Link } from 'react-router'

// Temporary. See docs/decisions/02-i18n
import t from 'src/i18n/locales/en/translation.json'
import { GITHUB_REPO_BASE } from 'src/constants'
import { useTranslation } from 'react-i18next'

interface FeatureProps {
icon: React.ElementType
Expand Down Expand Up @@ -46,9 +46,11 @@ const HeroFeature: React.FC<FeatureProps> = ({ icon: Icon, title, description, l
}

export const Hero = () => {
const { t } = useTranslation()

return (
<div className="flex bg-gradient-to-b from-slate-600 to-slate-900">
<section className="w-full py-8 text-white">
<div className="w-full py-8 text-white">
<div className="container px-4 md:px-6">
<div className="grid items-center gap-6 lg:grid-cols-2">
<div className="flex flex-col justify-center space-y-4 text-center">
Expand All @@ -57,21 +59,20 @@ export const Hero = () => {
<CognactiveIcon className="fill-white" darkMode />
</div>
<h1 className="my-4 text-3xl font-bold tracking-tighter text-transparent text-white sm:text-5xl xl:text-6xl/none">
{t['hero-title']}
{t('homepage.hero-title')}
</h1>
<p className="mb-4">Open source tools and tech for the anti-fungal NAC protocol.</p>
<p className="mb-4">{t('homepage.hero-subtitle')}</p>
<div className="mx-auto flex max-w-sm flex-col gap-2">
<Button className="bg-cyan py-6 text-lg font-semibold" size={'lg'} asChild>
<Link to="/tracker" viewTransition>
<ListTodo className="mr-2" />
Regimen Tracker
{/* {t('get-started')} */}
{t('homepage.tracker-link-title')}
</Link>
</Button>
<Button className="py-6 text-lg font-semibold" size={'lg'} asChild>
<Link to="/ingread" viewTransition>
<Scan className="mr-2" />
Ingredient Scanner
{t('homepage.ingredient-scanner-link-title')}
</Link>
</Button>
{/* <Button className="py-6 text-lg font-semibold" size={'lg'} asChild>
Expand All @@ -83,20 +84,20 @@ export const Hero = () => {
<Button className="py-6 text-lg font-semibold" size={'lg'} asChild>
<Link to="/fungcaster" viewTransition>
<MonitorPlay className="mr-2" />
Fungcaster
{t('homepage.fungcaster')}
</Link>
</Button>
<Button className="py-6 text-lg font-semibold" size={'lg'} asChild>
<Button className="text-md py-6 font-semibold" size={'lg'} asChild>
<Link to="/memery/platos-cave" viewTransition>
<ScrollIcon className="mr-2" />
Memery Stream
<FlameIcon className="mr-2" />
{t('homepage.cave-allegory')}
</Link>
</Button>
<div>
<p className="my-2">What is the NAC protocol?</p>
<p className="my-2">{t('homepage.whitepaper-label')}</p>
<Button size={'lg'} variant={'ghost'} className="border border-white text-white shadow-md" asChild>
<a href="https://www.docdroid.net/4H1DgHE/nac-protocol-printable-pdf">
<ScrollIcon className="mr-2 inline-block w-4" /> Read NAC protocol whitepaper
<ScrollIcon className="mr-2 inline-block w-4" /> {t('homepage.whitepaper-prompt')}
</a>
</Button>
</div>
Expand All @@ -107,26 +108,26 @@ export const Hero = () => {
<div className="grid grid-cols-1 gap-2">
<HeroFeature
icon={() => <ActivitySquare size={16} />}
title={t['tracking']}
description={t['tracking-desc']}
title={t('homepage.tracking')}
description={t('homepage.tracking-desc')}
/>
<HeroFeature
icon={() => <LayoutGridIcon size={16} />}
title={t['hero-appexperience']}
description={t['hero-appexperience-desc']}
title={t('homepage.hero-appexperience')}
description={t('homepage.hero-appexperience-desc')}
/>
<HeroFeature
icon={() => <HeartHandshakeIcon size={16} />}
title={t['hero-opensource']}
description={t['hero-opensource-desc']}
title={t('homepage.hero-opensource')}
description={t('homepage.hero-opensource-desc')}
link={GITHUB_REPO_BASE}
linkText="Source code available here."
/>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
)
}
Loading