feat(ui): add Button, Modal, Spinner, Toast, and Tooltip components with styles
All checks were successful
Build and Release / build-and-release (push) Successful in 13m12s
All checks were successful
Build and Release / build-and-release (push) Successful in 13m12s
- Implemented Button component with various props for customization. - Created Modal component with header, content, and footer subcomponents. - Added Spinner component for loading indicators. - Developed Toast component for displaying notifications. - Introduced Tooltip component for contextual hints with keyboard shortcuts. - Added corresponding CSS modules for styling each component. - Updated index file to export new components. - Configured TypeScript settings for the UI package.
This commit is contained in:
111
CLAUDE.md
111
CLAUDE.md
@@ -1,3 +1,7 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
**Update this file when making significant changes.**
|
||||
|
||||
See also: [CONVEX_RULES.md](./CONVEX_RULES.md) | [CONVEX_EXAMPLES.md](./CONVEX_EXAMPLES.md)
|
||||
@@ -15,6 +19,33 @@ See also: [CONVEX_RULES.md](./CONVEX_RULES.md) | [CONVEX_EXAMPLES.md](./CONVEX_E
|
||||
- **E2E Encryption**: Platform-specific crypto (Electron: Node crypto via IPC, Web: Web Crypto API)
|
||||
- **File Storage**: Convex built-in storage (`generateUploadUrl` + `getUrl`)
|
||||
|
||||
## Development Commands
|
||||
|
||||
```bash
|
||||
# Install
|
||||
npm install # Installs all workspaces
|
||||
|
||||
# Backend
|
||||
npx convex dev # Start Convex dev server (creates .env.local)
|
||||
|
||||
# Frontend (run alongside backend)
|
||||
npm run dev:web # Web app at localhost:5173
|
||||
npm run dev:electron # Electron app (Vite + Electron concurrently)
|
||||
|
||||
# Production builds
|
||||
npm run build:web # Web production build -> apps/web/dist
|
||||
npm run build:electron # Electron build with electron-builder
|
||||
npm run build:android # Web build + Capacitor sync
|
||||
|
||||
# Android
|
||||
cd apps/android && npx cap sync && npx cap open android
|
||||
|
||||
# Preview
|
||||
cd apps/web && npx vite preview # Preview web production build
|
||||
```
|
||||
|
||||
**No test framework or linter is configured in this project.**
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
@@ -33,66 +64,29 @@ Discord Clone/
|
||||
│ │ ├── App.jsx # Router + AuthGuard
|
||||
│ │ └── index.css # Global styles
|
||||
│ └── platform-web/ # Web/Capacitor platform implementations
|
||||
│ └── src/
|
||||
│ ├── crypto.js # Web Crypto API (RSA-OAEP, Ed25519, AES-256-GCM, scrypt)
|
||||
│ ├── session.js # localStorage session persistence
|
||||
│ ├── settings.js # localStorage settings
|
||||
│ ├── idle.js # Page Visibility API idle detection
|
||||
│ └── index.js # Bundles all web platform APIs
|
||||
│ └── src/ # Web Crypto API, localStorage session/settings, Page Visibility idle
|
||||
├── apps/
|
||||
│ ├── electron/ # Electron desktop app
|
||||
│ │ ├── main.cjs # Electron main process
|
||||
│ │ ├── preload.cjs # IPC bridge (window.* APIs)
|
||||
│ │ ├── updater.cjs # electron-updater integration
|
||||
│ │ ├── splash.html # Update splash screen
|
||||
│ │ └── src/
|
||||
│ │ ├── main.jsx # Entry: PlatformProvider + HashRouter
|
||||
│ │ └── platform/index.js # Electron platform (delegates to window.* APIs)
|
||||
│ ├── web/ # Web browser app
|
||||
│ │ └── src/
|
||||
│ │ └── main.jsx # Entry: PlatformProvider + BrowserRouter
|
||||
│ ├── electron/ # Electron desktop app (main.cjs, preload.cjs, updater.cjs)
|
||||
│ │ └── src/main.jsx # Entry: PlatformProvider + HashRouter
|
||||
│ ├── web/ # Web browser app (PWA enabled via VitePWA)
|
||||
│ │ └── src/main.jsx # Entry: PlatformProvider + BrowserRouter
|
||||
│ └── android/ # Capacitor Android wrapper
|
||||
│ └── capacitor.config.ts # Points webDir at ../web/dist
|
||||
├── package.json # Root workspace config
|
||||
├── .env.local # Convex + LiveKit + Tenor keys
|
||||
├── .env.local # Convex + LiveKit + Klipy keys
|
||||
└── CLAUDE.md
|
||||
```
|
||||
|
||||
## Key Convex Files (convex/)
|
||||
## Vite & Import Aliases
|
||||
|
||||
- `schema.ts` - Full schema: userProfiles (with avatarStorageId, aboutMe, customStatus, joinSoundStorageId), categories (name, position), channels (with categoryId, topic, position), messages, messageReactions, channelKeys, roles, userRoles, invites, dmParticipants, typingIndicators, voiceStates, channelReadState, serverSettings (serverName, afkChannelId, afkTimeout, iconStorageId)
|
||||
- `auth.ts` - getSalt, verifyUser, createUserWithProfile, getPublicKeys (includes avatarUrl, aboutMe, customStatus, joinSoundUrl), updateProfile (includes joinSoundStorageId, removeJoinSound), updateStatus, getMyJoinSoundUrl
|
||||
- `categories.ts` - list, create, rename, remove, reorder
|
||||
- `channels.ts` - list, get, create (with categoryId/topic/position), rename, remove (cascade), updateTopic, moveChannel, reorderChannels
|
||||
- `members.ts` - getChannelMembers (includes isHoist on roles, avatarUrl, aboutMe, customStatus)
|
||||
- `channelKeys.ts` - uploadKeys, getKeysForUser
|
||||
- `messages.ts` - list (with reactions + username), send, edit, pin, listPinned, remove (with manage_messages permission check)
|
||||
- `reactions.ts` - add, remove
|
||||
- `serverSettings.ts` - get (resolves iconUrl), update (manage_channels permission), updateName (manage_channels permission), updateIcon (manage_channels permission), clearAfkChannel (internal)
|
||||
- `typing.ts` - startTyping, stopTyping, getTyping, cleanExpired (scheduled)
|
||||
- `dms.ts` - openDM, listDMs
|
||||
- `invites.ts` - create, use, revoke
|
||||
- `roles.ts` - list, create, update, remove, listMembers, assign, unassign, getMyPermissions
|
||||
- `voiceState.ts` - join, leave, updateState, getAll (includes joinSoundUrl per user), afkMove (self-move to AFK channel)
|
||||
- `voice.ts` - getToken (Node action, livekit-server-sdk)
|
||||
- `files.ts` - generateUploadUrl, getFileUrl
|
||||
- `gifs.ts` - search, categories (Node actions, Tenor API)
|
||||
- `readState.ts` - getReadState, markRead, getAllReadStates, getLatestMessageTimestamps (unread tracking)
|
||||
All Vite configs use `envDir: '../../'` to pick up root `.env.local`.
|
||||
|
||||
## Shared Frontend (packages/shared/src/)
|
||||
| Alias | Resolves to |
|
||||
|-------|-------------|
|
||||
| `@discord-clone/shared` | `packages/shared/src/` |
|
||||
| `@discord-clone/platform-web` | `packages/platform-web/src/` |
|
||||
| `@shared` | `packages/shared/src/` |
|
||||
|
||||
- `App.jsx` - Router + AuthGuard (uses `usePlatform().session`)
|
||||
- `pages/Login.jsx` - Convex auth (uses `usePlatform().crypto` for key derivation)
|
||||
- `pages/Register.jsx` - Convex auth (uses `usePlatform().crypto` for key generation)
|
||||
- `pages/Chat.jsx` - useQuery for channels, categories, channelKeys, DMs
|
||||
- `components/ChatArea.jsx` - Messages, typing, reactions via Convex queries/mutations
|
||||
- `components/Sidebar.jsx` - Channel/category creation, key distribution, invites, drag-and-drop via @dnd-kit
|
||||
- `contexts/VoiceContext.jsx` - Voice state via Convex + LiveKit + idle detection via `usePlatform().idle`
|
||||
- `components/TitleBar.jsx` - Conditional: only renders if `platform.features.hasWindowControls`
|
||||
- `components/UpdateBanner.jsx` - Conditional: only renders if `platform.features.hasNativeUpdates`
|
||||
- `components/ScreenShareModal.jsx` - Uses `usePlatform().screenCapture`
|
||||
- `components/MessageItem.jsx` - Message rendering, link opening via `usePlatform().links`
|
||||
- `platform/PlatformProvider.jsx` - React context providing platform APIs via `usePlatform()` hook
|
||||
Convex imports from shared components use relative path `../../../../convex/_generated/api` (4 levels up from shared src subdirs).
|
||||
|
||||
## Platform Abstraction (usePlatform())
|
||||
|
||||
@@ -112,7 +106,6 @@ All platform-specific APIs are accessed via the `usePlatform()` hook:
|
||||
- Channel IDs use Convex `_id` (not `id`) - all references use `channel._id`
|
||||
- Auth: client hashes DAK -> HAK before sending, server does string comparison
|
||||
- First user bootstrap: createUserWithProfile creates Owner + @everyone roles
|
||||
- Vite configs use `envDir: '../../'` to pick up root `.env.local`
|
||||
- Convex queries are reactive - no need for manual refresh or socket listeners
|
||||
- File uploads use Convex storage: `generateUploadUrl` -> POST blob -> `getFileUrl`
|
||||
- Typing indicators use scheduled functions for TTL cleanup
|
||||
@@ -126,6 +119,10 @@ All platform-specific APIs are accessed via the `usePlatform()` hook:
|
||||
- Custom join sounds: stored as `joinSoundStorageId` on `userProfiles`
|
||||
- Server icon: `serverSettings` stores `iconStorageId`, resolved to `iconUrl`
|
||||
- `userPreferences.js` `setUserPref` takes optional `settings` param for disk persistence via platform
|
||||
- Module-scope functions needing crypto accept it as parameter (e.g., `encryptKeyForUsers(users, channelId, keyHex, crypto)`)
|
||||
- `randomBytes(size)` returns hex string on both platforms
|
||||
- Keys exchanged as PEM strings (SPKI public, PKCS8 private) for cross-platform interop
|
||||
- TitleBar/UpdateBanner render conditionally based on `platform.features.*`
|
||||
|
||||
## Environment Variables
|
||||
|
||||
@@ -135,12 +132,4 @@ In `.env.local` at project root:
|
||||
- `VITE_LIVEKIT_URL` - LiveKit server URL
|
||||
- `LIVEKIT_API_KEY` - LiveKit API key (used in Convex Node action)
|
||||
- `LIVEKIT_API_SECRET` - LiveKit API secret (used in Convex Node action)
|
||||
- `TENOR_API_KEY` - Tenor GIF API key (used in Convex Node action)
|
||||
|
||||
## Running the App
|
||||
|
||||
1. `npm install` (installs all workspaces)
|
||||
2. `npx convex dev` (starts Convex backend, creates `.env.local`)
|
||||
3. Electron: `npm run dev:electron`
|
||||
4. Web: `npm run dev:web`
|
||||
5. Android: `npm run build:web && cd apps/android && npx cap sync && npx cap open android`
|
||||
- `KLIPY_API_KEY` - Klipy GIF API customer id (used in `convex/gifs.ts`). Replaces the old `TENOR_API_KEY` after Tenor's shutdown — the legacy var name is still read as a fallback so existing deployments only need to swap the value.
|
||||
|
||||
Reference in New Issue
Block a user