# Expertise Areas
- Solidity, TypeScript, Node.js, Next.js 14 App Router, React, Vite, Viem v2, Wagmi v2, Shadcn UI, Radix UI, Tailwind Aria
# Key Principles
- Write concise, technical responses with TypeScript examples
- Use functional, declarative programming; avoid classes
- Prefer iteration and modularization over duplication
- Use descriptive variable names with auxiliary verbs (e.g., isLoading)
- Use lowercase with dashes for directories (e.g., components/auth-wizard)
- Favor named exports for components
- Use the RORO (Receive an Object, Return an Object) pattern
# JavaScript/TypeScript
- Use "function" keyword for pure functions; omit semicolons
- Use TypeScript; prefer interfaces over types; avoid enums, use maps
- Structure files: Exported component, subcomponents, helpers, static content, types
- Avoid unnecessary curly braces in conditionals
- Use concise, one-line syntax for simple conditionals
- Prioritize error handling and edge cases:
- Handle errors early; use early returns
- Place happy path last for readability
- Avoid unnecessary else statements; use if-return pattern
- Use guard clauses for preconditions and invalid states
- Implement proper error logging and user-friendly messages
- Throw errors from @opyn/errors
# Dependencies
- Next.js 14 App Router, Wagmi v2, Viem v2
# React/Next.js
- Use functional components
- Use TypeScript interfaces at the bottom of files
- Name everything semantically
- Use types from @opyn/supabase
- Use declarative JSX
- Use function, not const for components
- Use const for methods in components
- Use variables for static content at the bottom of files
- Use Shadcn UI, Radix, Tailwind Aria for styling importing components from @opyn/ui
- Implement responsive design with Tailwind CSS; mobile-first approach
- Use content variables for static content outside render functions
- Minimize 'use client', 'useEffect', 'setState'; favor RSC
- Use Zod for form validation
- Optimize images: WebP format, size data, lazy loading
- Use error boundaries for unexpected errors
- Use useActionState with react-hook-form for form validation with <Form>
- Use react functional components never render functions
- Use guard clauses and early returns for validation and preconditions
```typescript
useEffect(() => {
if (!watch) return
refetch()
}, [refetch, watch])
```
- Use next-safe-action for server actions:
- Handle errors gracefully with captureAppError from @opyn/errors
- Use ActionResult for consistent responses
- Use next-safe-action for all server actions:
- Handle errors gracefully and return appropriate responses using captureAppError.
- Implement consistent error handling and success responses using @lib/actions.ts
- Example:
```typescript
'use server'
import { type ActionResult, success, failure } from '@opyn/lib'
import { createSupabaseNextClient } from '@/services/supabase'
import { type Tables, depositInsertSchema } from '@opyn/supabase'
import { createSafeActionClient } from 'next-safe-action'
// Stores a deposit intent in the database, creating a pending transaction and a deposit record.
export const saveDepositIntent = createSafeActionClient()
.schema(depositInsertSchema)
.action(
async ({
parsedInput: transfer,
}): Promise<ActionResult<Tables<'presale_deposit'>>> => {
try {
const supabase = await createSupabaseNextClient()
const transaction = await supabase
.from('transaction')
.upsert(
{
hash: transfer.deposit_hash,
trx_type: 'presale_deposit',
...transfer,
},
{ onConflict: 'hash' },
)
.select()
if (transaction.error)
return failure('DB_OP_FAILURE', transaction.error)
return success(deposit.data[0])
} catch (error) {
return failure('UNEXPECTED_ERROR', error)
}
},
)
```
# Key Next.js Conventions
1. Rely on Next.js App Router for state changes
2. Prioritize Web Vitals (LCP, CLS, FID)
3. Minimize 'use client' usage:
- Prefer server components and Next.js SSR features
- Use 'use client' only for Web API access in small components
- Avoid 'use client' for data fetching or state management
# Reference
- Next.js documentation for Data Fetching, Rendering, and Routing best practices
- Radix UI documentation for accessibility and component primitives
- Shadcn UI documentation for component system and starter kit
# UI Component Guidelines
## Icons and Design System
- Use Heroicons (https://heroicons.com/) for all icons
- Follow design system tokens in app/globals.css and tailwind.config.js
- Avoid default Tailwind colors to maintain brand consistency
## Styling Best Practices
- Always use `cn` utility function for conditional class names
- Add `className` prop to components for external styling overrides
- Use `cva` function for component variants to centralize styling
- Utilize CSS Flexbox and Grid for modern layouts
- Build fluid, responsive components with container queries
- Ensure mobile-first responsive design with Tailwind classes
## Component Architecture
- Use Radix UI's built-in functions (e.g. `onOpenChange`) over custom logic
- Style with Radix UI data attributes for interactivity
- Prioritize Radix UI data attributes for accessibility
- Use Tailwind CSS classes for styling
- Avoid JavaScript styling unless absolutely necessary
## Component Usage
- Use `<Form>` instead of native `<form>`
- Use shadcn/ui Sidebar for menu navigation
- Use shadcn/ui Sheet for contextual dialogs/panels
- Use shadcn/ui Skeleton in Suspense fallbacks
- Use Sonner for toast notifications
# Tools
- Use React-use for hooks
- Use date-fns for date manipulation
### Examples
#### Example 1: Styling a Tab Component
```tsx
<Tab
data-state="active"
className={cn(
"text-sm font-medium",
"data-[state=active]:text-brand-500",
"data-[state=inactive]:text-brand-400"
)}
>
Tab 1
</Tab>
```
#### Example 2: Responsive Grid Layout
```tsx
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<Card />
<Card />
<Card />
</div>
```
#### Example 3: Using Radix's `onOpenChange`
```tsx
<Dialog onOpenChange={(isOpen) => console.log(isOpen)}>
<DialogTrigger asChild>
<Button>Open Dialog</Button>
</DialogTrigger>
<DialogContent>Content goes here</DialogContent>
</Dialog>
```
# Viem Wagmi
- Use getAddress from viem to convert addresses to Address type, never as Address
- Use viem 2.x apis
- Use wagmi 2.x apis
# NextJS Services
- never use try/catch in services instead use captureAppError from @opyn/errors
- use SupaApiParams type for supabase client
- return type guards for supabase queries and mutations
- return data type can never be null
```tsx
import { captureAppError } from '@opyn/errors'
import type { SupaApiParams } from '../types'
export async function getAssetByUuid({
uuid,
supabase,
}: SupaApiParams & {
uuid: string
}) {
const { data, error } = await supabase
.from('token')
.select()
.eq('uuid', uuid)
.single()
if (error || !data) captureAppError('FETCH_ERROR', error)
return data as NonNullable<typeof data>
}
export async function insertMarket({
market,
supabase,
}: SupaApiParams & {
market: TablesInsert<'market'>
}) {
const { data, error } = await supabase
.from('market')
.insert(market)
.select()
.single()
if (error || !data) captureAppError('INSERT_ERROR', error)
return data as NonNullable<typeof data>
}
```
# Trigger Hooks
```ts
import type { AlchemyActivity, AlchemyWebhookEvent } from '@opyn/alchemy'
import { logger, task } from '@trigger.dev/sdk/v3'
import { insertMarket } from '../../lib/supabase'
export const newMarketTask = task({
id: 'collateral-deposit',
run: async (payload: AlchemyWebhookEvent) => {
const activity: AlchemyActivity[] = payload.event.activity
try {
const market = await insertCollateralDeposit({ market: dummyMarket })
logger.info('Inserted dummy market', { market })
} catch (error) {
logger.error('Failed to insert dummy market', { error })
}
},
})
```
css
golang
html
java
javascript
less
next.js
plpgsql
+8 more
First Time Repository
TypeScript
Languages:
CSS: 26.1KB
HTML: 0.4KB
JavaScript: 3.9KB
PLpgSQL: 7.6KB
TypeScript: 1890.7KB
Created: 11/21/2024
Updated: 11/27/2024