{"id":33510046,"url":"https://github.com/jongan69/expo-cloudinary-image-manager","last_synced_at":"2026-05-10T21:41:09.913Z","repository":{"id":325048988,"uuid":"1099623668","full_name":"jongan69/expo-cloudinary-image-manager","owner":"jongan69","description":"An Expo React Native App for Managing Cloudinary Images","archived":false,"fork":false,"pushed_at":"2025-11-19T09:36:22.000Z","size":233,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-11-19T10:16:00.664Z","etag":null,"topics":["cloudinary","expo","image-processing","react-native"],"latest_commit_sha":null,"homepage":"https://expo-cloudinary-image-manager--eilrmlsqif.expo.app/sign-in","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jongan69.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-19T08:28:27.000Z","updated_at":"2025-11-19T09:36:25.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jongan69/expo-cloudinary-image-manager","commit_stats":null,"previous_names":["jongan69/expo-cloudinary-image-manager"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/jongan69/expo-cloudinary-image-manager","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jongan69%2Fexpo-cloudinary-image-manager","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jongan69%2Fexpo-cloudinary-image-manager/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jongan69%2Fexpo-cloudinary-image-manager/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jongan69%2Fexpo-cloudinary-image-manager/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jongan69","download_url":"https://codeload.github.com/jongan69/expo-cloudinary-image-manager/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jongan69%2Fexpo-cloudinary-image-manager/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286079811,"owners_count":27282121,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-26T02:00:06.075Z","response_time":193,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cloudinary","expo","image-processing","react-native"],"created_at":"2025-11-26T04:03:19.535Z","updated_at":"2025-11-26T04:03:23.855Z","avatar_url":"https://github.com/jongan69.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Cloudinary Image Manager\n\nA modern, cross-platform Expo React Native application for managing Cloudinary images. Upload, view, and edit image metadata with a beautiful, performant interface that works seamlessly on iOS, Android, and Web.\n\n## ✨ Features\n\n- 🔐 **Secure Authentication** - Email-based sign-in with Clerk (verification code flow)\n- 📸 **Image Upload** - Direct upload to Cloudinary using unsigned upload presets\n- 🖼️ **Photo Gallery** - Browse images with optimized thumbnails and smooth scrolling\n- ✏️ **Metadata Editing** - Edit descriptions and tags for existing images\n- 🔒 **Secure Storage** - API credentials stored securely using Expo SecureStore\n- ⚡ **Performance Optimized** - FlashList for native, FlatList for web, image caching, and optimized Cloudinary URLs\n- 🌐 **Cross-Platform** - Works on iOS, Android, and Web with platform-specific optimizations\n- 💾 **Offline Support** - Photo list caching for faster load times\n\n## 🛠️ Tech Stack\n\n- **Framework**: Expo SDK 54 with React Native 0.81.5\n- **Routing**: Expo Router (file-based routing)\n- **Authentication**: Clerk Expo SDK (email verification code)\n- **Image Management**: Cloudinary API\n- **Image Loading**: expo-image (optimized image component)\n- **Lists**: @shopify/flash-list (native) / FlatList (web)\n- **Storage**: \n  - AsyncStorage (non-sensitive data)\n  - Expo SecureStore (API credentials)\n- **Language**: TypeScript\n- **Styling**: React Native StyleSheet\n\n## 📋 Prerequisites\n\n- **Node.js** 18+ and **Bun** (or npm/yarn)\n- **Expo CLI**: `bun install -g expo-cli` (optional, but recommended)\n- **Cloudinary Account** - [Sign up here](https://cloudinary.com/)\n- **Clerk Account** - [Sign up here](https://clerk.com/) for authentication\n\n## 🚀 Installation\n\n1. **Clone the repository** (or navigate to the project directory)\n\n2. **Install dependencies**:\n```bash\nbun install\n```\n\n3. **Set up environment variables**:\nCreate a `.env` file in the root directory:\n```env\nEXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=your_clerk_publishable_key_here\n```\n\n4. **Start the development server**:\n```bash\n# Start Expo dev server\nbun start\n\n# Or for web specifically\nbun web\n# or\nbun dev:web\n```\n\n## ⚙️ Configuration\n\n### Cloudinary Setup\n\n1. **Create an Unsigned Upload Preset**:\n   - Log into your [Cloudinary Dashboard](https://cloudinary.com/console)\n   - Navigate to **Settings → Upload → Upload presets**\n   - Click **\"Add upload preset\"**\n   - Set **Signing mode** to **\"Unsigned\"**\n   - Configure **Folder**: `Modeling` (or your preferred folder)\n   - Save and note the preset name\n\n2. **Get API Credentials** (for metadata editing):\n   - In Cloudinary Dashboard, go to **Settings → Security**\n   - Copy your **API Key** and **API Secret**\n\n### Clerk Setup\n\n1. **Create a Clerk Application**:\n   - Sign up at [clerk.com](https://clerk.com/)\n   - Create a new application\n   - Configure authentication methods:\n     - Enable **\"Sign-in with email\"**\n     - Enable **\"Email verification code\"** as the sign-in method\n   - Copy your **Publishable Key** to `.env`\n\n## 📱 Usage\n\n### First Time Setup\n\n1. **Sign In**:\n   - Enter your email address\n   - Check your email for the verification code\n   - Enter the 6-digit code to sign in\n\n2. **Configure Cloudinary** (Settings tab):\n   - **Cloud Name** (required) - Your Cloudinary cloud name\n   - **Upload Preset** (required) - Your unsigned upload preset name\n   - **Folder Name** (optional) - Defaults to \"Modeling\"\n   - **API Key** (optional) - Required for editing metadata\n   - **API Secret** (optional) - Required for editing metadata\n\n### Uploading Images\n\n1. Navigate to the **Upload** tab\n2. Tap **\"Select Image\"** to choose from your device\n3. Optionally add:\n   - **Description** - Image description\n   - **Tags** - Comma-separated tags (e.g., `commercial, fashion, lifestyle`)\n4. Tap **\"Upload to Modeling Folder\"**\n5. Wait for upload confirmation\n\n### Viewing Photos\n\n1. Navigate to the **Photos** tab\n2. Browse your image gallery (optimized thumbnails)\n3. Pull down to refresh and fetch latest images\n4. Tap any image to view details\n\n### Editing Metadata\n\n1. Open a photo from the **Photos** tab\n2. Edit the **Description** and/or **Tags**\n3. Tap **\"Save Changes\"** when ready\n4. Changes are synced to Cloudinary\n\n**Note**: Metadata editing requires API Key and API Secret to be configured in Settings.\n\n## 🏗️ Architecture\n\n### Project Structure\n\n```\nsrc/\n├── app/\n│   ├── (auth)/              # Authentication routes\n│   │   ├── sign-in.tsx     # Email code sign-in\n│   │   └── sign-up.tsx     # User registration\n│   ├── (tabs)/             # Main app tabs\n│   │   ├── photos.tsx      # Photo gallery\n│   │   ├── upload.tsx      # Image upload\n│   │   └── settings.tsx    # Cloudinary configuration\n│   ├── photo/\n│   │   └── [id].tsx        # Photo detail/edit screen\n│   ├── api/                # API routes\n│   │   ├── fetch-photos+api.ts    # Fetch photos from Cloudinary\n│   │   └── update-metadata+api.ts  # Update image metadata\n│   └── _layout.tsx         # Root layout with ClerkProvider\n├── components/\n│   ├── SecureTextInput.tsx # Password input with visibility toggle\n│   └── SignOutButton.tsx   # Sign out component\n├── services/\n│   ├── cloudinaryService.ts # Cloudinary API integration\n│   └── uploadService.ts     # Image upload logic\n├── utils/\n│   ├── storage.ts          # Credential storage (SecureStore/AsyncStorage)\n│   ├── imageOptimization.ts # Cloudinary URL optimization\n│   └── photoCache.ts        # Photo list caching\n└── types/\n    └── index.ts            # TypeScript type definitions\n```\n\n### Key Components\n\n- **Expo Router**: File-based routing with nested layouts\n- **Clerk Provider**: Authentication context for the entire app\n- **API Routes**: Server-side functions for Cloudinary Admin API operations\n- **Image Optimization**: Automatic Cloudinary transformations for performance\n- **Caching**: AsyncStorage-based caching for photo lists (5-minute TTL)\n\n## ⚡ Performance Optimizations\n\n- **FlashList** (native) / **FlatList** (web) - High-performance list rendering\n- **expo-image** - Optimized image loading with disk caching\n- **Cloudinary Transformations** - Automatic format and quality optimization\n- **Photo List Caching** - Reduces API calls with 5-minute cache\n- **Memoized Components** - Prevents unnecessary re-renders\n- **Optimized Thumbnails** - Smaller images for grid view (300px)\n- **Lazy Loading** - Images load as you scroll\n\n## 🌐 Platform Support\n\n### Web\n- Runs in browser with `bun web` or `expo start --web`\n- Uses FlatList (FlashList doesn't support web)\n- SecureStore falls back to AsyncStorage\n- Full feature parity with native\n\n### iOS\n- Native performance with FlashList\n- SecureStore for encrypted credential storage\n- Native image picker integration\n\n### Android\n- Native performance with FlashList\n- SecureStore for encrypted credential storage\n- Native image picker integration\n\n## 🔧 Development\n\n### Available Scripts\n\n```bash\n# Development\nbun start              # Start Expo dev server\nbun web                # Start web development server\nbun dev:web            # Alias for web\nbun dev:android        # Run on Android device\nbun dev:ios            # Run on iOS device\nbun dev:tunnel         # Start with tunnel (for testing on physical devices)\n\n# Building\nbun prebuild           # Generate native projects\nbun build:all          # Build for all platforms (EAS)\nbun export:web         # Export web build\n\n# Code Quality\nbun lint               # Run ESLint\nbun lint:fix           # Fix ESLint errors\nbun format             # Format with Prettier\nbun format:fix         # Fix and format\nbun test               # Run tests\nbun test:watch         # Run tests in watch mode\n\n# Maintenance\nbun clean              # Clean node_modules and reinstall\nbun doc-fix            # Check and fix Expo package versions\n```\n\n### Development Tips\n\n- **Hot Reload**: Enabled by default in Expo\n- **Debugging**: Use React Native Debugger or Chrome DevTools\n- **API Routes**: Test API routes at `http://localhost:8081/api/fetch-photos` (web)\n- **Caching**: Clear cache by pulling down to refresh in Photos tab\n\n## 🚢 Production\n\n- **Env vars**: Ensure `.env` contains `EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY` before building.\n- **Native builds**: Run `bun build:all` (EAS) or the platform-specific EAS builds you need.\n- **Web bundle**: Run `bun export:web` to generate a static web build in `dist`.\n- **Versioning**: Update `app.json` version + build numbers before submitting to stores.\n- **Sanity checks**:\n  - `bun lint` (already configured via `.eslintrc.js`)\n  - Launch `bun start --no-dev` to spot prod-only issues.\n  - Test signing in/out on every platform after updating Clerk or Expo versions.\n\n## 🔒 Security\n\n- **API Credentials**: Stored in Expo SecureStore (encrypted, native) or AsyncStorage (web fallback)\n- **Upload Presets**: Stored in AsyncStorage (non-sensitive)\n- **Unsigned Uploads**: No server required for image uploads\n- **API Routes**: Server-side operations require API credentials\n- **Clerk Authentication**: Secure session management\n\n## 🐛 Troubleshooting\n\n### \"No credentials\" error\n- Ensure Cloudinary credentials are configured in Settings\n- Check that Cloud Name and Upload Preset are correct\n\n### \"API credentials required\" error\n- Add Cloudinary API Key and Secret in Settings to enable metadata editing\n- Verify credentials are correct in Cloudinary Dashboard\n\n### Upload fails\n- Verify Upload Preset is set to \"Unsigned\" in Cloudinary\n- Check folder name matches your Cloudinary folder structure\n- Ensure you have internet connectivity\n\n### Photos not loading\n- Verify API Key and Secret are configured in Settings\n- Check folder name is correct\n- Try pulling down to refresh\n- Check browser console (web) or Metro logs for errors\n\n### Sign-in issues\n- Verify `EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY` is set in `.env`\n- Check that Clerk is configured for email verification code sign-in\n- Ensure you're checking the correct email for verification codes\n\n### Image optimization errors (400 Bad Request)\n- Fixed: Width values are now rounded to integers (Cloudinary requirement)\n- If issues persist, check Cloudinary transformation syntax\n\n### Hooks order errors\n- Fixed: All hooks are now called before any early returns\n- If you see this error, ensure hooks are always called in the same order\n\n## 📦 Dependencies\n\n### Core\n- `expo` ^54.0.0\n- `expo-router` ~6.0.15\n- `react` 19.1.0\n- `react-native` 0.81.5\n\n### Authentication\n- `@clerk/clerk-expo` ^2.19.2\n\n### Image Management\n- `expo-image` ^3.0.10\n- `cloudinary` ^2.8.0\n\n### Performance\n- `@shopify/flash-list` ^2.2.0\n- `@react-native-async-storage/async-storage` ^2.2.0\n- `expo-secure-store` ~15.0.7\n\n### UI\n- `expo-image-picker` ~17.0.8\n- `react-native-safe-area-context` ~5.6.0\n\n## 📄 License\n\nPrivate project\n\n## 🤝 Contributing\n\nThis is a private project. For issues or questions, please contact the project maintainer.\n\n---\n\nBuilt with ❤️ using Expo and React Native","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjongan69%2Fexpo-cloudinary-image-manager","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjongan69%2Fexpo-cloudinary-image-manager","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjongan69%2Fexpo-cloudinary-image-manager/lists"}