Skip to content
Merged
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
a547259
Add logging and auth checks for submissions
ghostleek Feb 10, 2026
0a3d029
Update auth-client.ts
ghostleek Feb 11, 2026
4d588be
Create useSwipe.ts
ghostleek Feb 11, 2026
aff6076
Update App.tsx
ghostleek Feb 11, 2026
6a93e58
fix: update api endpoint
ghostleek Feb 11, 2026
345d5e8
fix: make generate slug from email a helper function
ghostleek Feb 11, 2026
d0c8b8b
add profile modal and dashboard theming
ghostleek Feb 11, 2026
30b1556
update dashboard styling
ghostleek Feb 11, 2026
e8907c0
update dashboard styling
ghostleek Feb 11, 2026
931154c
Update UserDashboard.tsx
ghostleek Feb 11, 2026
5ac20cc
Update UserDashboard.tsx
ghostleek Feb 11, 2026
411866f
fix: favicon, apple touch styling
ghostleek Feb 11, 2026
25fb3ea
remove unnecessary files in brand guidelines
ghostleek Feb 11, 2026
a5de68f
add styling md, update profile, can remove some components
ghostleek Feb 11, 2026
b6cd897
styling profile
ghostleek Feb 11, 2026
64b82cd
fix: profile styling
ghostleek Feb 12, 2026
1d13259
fix: abstract styling to components instead, update claude.md
ghostleek Feb 12, 2026
2fd2e01
styling and claude.md updates
ghostleek Feb 12, 2026
3c9901b
better abstracted components
ghostleek Feb 12, 2026
dfb0f93
add analytics
ghostleek Feb 12, 2026
ec5447f
create modal
ghostleek Feb 12, 2026
c1ce876
fix dashboard syntax error
ghostleek Feb 12, 2026
ca848d8
fix circle truncate instead of full user name
ghostleek Feb 12, 2026
da8e7a2
better mobile auth menu, event listener, footer truncation
ghostleek Feb 12, 2026
5f22768
improve swipe interaction
ghostleek Feb 12, 2026
783ec1b
update changelog, my submissions UIUX,
ghostleek Feb 12, 2026
d27bea4
fix: app submissions to add user if not auto added
ghostleek Feb 12, 2026
faef3b8
submission fix
ghostleek Feb 12, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ dist/
*.log
.DS_Store
.vercel
.env*.local
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1,720 changes: 0 additions & 1,720 deletions Brand Guidelines/7. Social Media Kit/6. Cover Pictures Source FIles/Cover_ai.ai

This file was deleted.

Binary file not shown.

This file was deleted.

Binary file not shown.
Binary file removed Brand Guidelines/String Brand Guidelines.pdf
Binary file not shown.
Binary file removed Brand Guidelines/String Brand Showcase.pdf
Binary file not shown.
82 changes: 82 additions & 0 deletions STYLING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# String Design System

## Color Palette

### Primary Colors
- **String Mint**: `#75F8CC` (RGB: 117, 248, 204)
- **String Dark**: `#33373B` (RGB: 51, 55, 59)
- **String Light**: `#C0F4FB` (RGB: 192, 244, 251)
- **White**: `#FFFFFF`

### Usage Guidelines
- **String Mint**: Primary accent, buttons, highlights, active states
- **String Dark**: Text, backgrounds, headers
- **String Light**: Secondary accents, subtle backgrounds
- **White**: Main backgrounds, cards

## Component Patterns

### Buttons
```tsx
// Primary Button
className="bg-string-mint text-string-dark hover:bg-string-mint-light px-4 py-2 rounded-xl font-medium transition-colors"

// Secondary Button
className="bg-white border border-gray-200 text-string-dark hover:bg-gray-50 px-4 py-2 rounded-xl font-medium transition-colors"

// Text Button
className="text-string-mint hover:text-string-mint-light font-medium transition-colors"
```

### Cards
```tsx
className="bg-white rounded-xl p-6 shadow-sm border border-gray-100 hover:border-string-mint transition-colors"
```

### Headers
```tsx
// Main Header
className="text-3xl font-bold text-string-dark"

// Section Header
className="text-xl font-semibold text-string-dark"

// Card Header
className="text-lg font-medium text-string-dark"
```

### Interactive Elements
- **Hover states**: Use `hover:border-string-mint` for subtle interactions
- **Active states**: Use `border-string-mint text-string-mint` for selection
- **Transitions**: Always include `transition-colors` or `transition-all duration-200`

## Layout Standards

### Spacing
- **Container max-width**: `max-w-4xl` or `max-w-7xl`
- **Section spacing**: `space-y-6` or `space-y-8`
- **Card padding**: `p-6` or `p-8`
- **Button padding**: `px-4 py-2` or `px-6 py-3`

### Border Radius
- **Cards**: `rounded-xl`
- **Buttons**: `rounded-xl`
- **Small elements**: `rounded-lg`
- **Avatars**: `rounded-2xl`

### Typography
- **Display**: `text-3xl font-bold`
- **Heading**: `text-xl font-semibold`
- **Subheading**: `text-lg font-medium`
- **Body**: `text-sm` or `text-base`
- **Caption**: `text-xs`

## Theme Support
Components should support both light and dark themes using the `t()` helper function:

```tsx
const t = (light: string, dark: string) => isDark ? dark : light;

// Usage
className={`${t('bg-white', 'bg-string-dark')} ${t('text-string-dark', 'text-white')}`}
```
32 changes: 26 additions & 6 deletions api/submissions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { neon } from '@neondatabase/serverless';
import { drizzle } from 'drizzle-orm/neon-http';
import { appSubmissions } from '../src/db/schema';
import { appSubmissions, users } from '../src/db/schema';
import { eq, desc } from 'drizzle-orm';

// Edge runtime configuration
Expand Down Expand Up @@ -61,18 +61,35 @@ export default async function handler(request: Request) {
);
}

// Ensure user exists in database before creating submission
const existingUser = await db
.select()
.from(users)
.where(eq(users.id, submittedByUserId))
.limit(1);

if (existingUser.length === 0) {
// Create user if doesn't exist
await db.insert(users).values({
id: submittedByUserId,
email: submittedByEmail,
name: null,
slug: null,
});
}
Comment on lines +64 to +79
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

Creating a placeholder user with slug: null can permanently break profile routing: api/users.ts only generates a slug when the user does not already exist, and does not backfill a missing slug on updates. If a user is inserted here first, they may never get a slug. Suggest reusing the same slug-generation/upsert logic as api/users.ts (or update existing users where slug IS NULL).

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback


// Insert submission
await db.insert(appSubmissions).values({
name,
url,
description,
logoUrl,
category,
description: description || null,
logoUrl: logoUrl || null,
category: category || null,
submittedByUserId,
submittedByEmail,
status: 'pending'
});


return new Response(
JSON.stringify({ success: true, message: 'App submitted for review' }),
{
Expand Down Expand Up @@ -123,7 +140,10 @@ export default async function handler(request: Request) {
} catch (error) {
console.error('Database error:', error);
return new Response(
JSON.stringify({ error: 'Internal server error' }),
JSON.stringify({
error: 'Internal server error',
details: error instanceof Error ? error.message : 'Unknown error'
}),
Comment on lines 140 to +146
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

The 500 response includes details derived from error.message. In production this can leak internal/database details to clients. Consider logging the detailed error server-side only and returning a generic error response (or gate details behind a development flag).

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

{
status: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
Expand Down
88 changes: 76 additions & 12 deletions claude.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# String.sg v2 - Development Plan

**Last Updated:** 2026-02-01
**Status:** Phase 3 Ready - Backend + Frontend Complete
**Last Updated:** 2026-02-12
**Status:** Phase 4 Complete + UI Design System Established

---

Expand Down Expand Up @@ -43,6 +43,17 @@
- [x] App submission form for authenticated users
- [x] User dashboard with profile/submissions/submit tabs
- [x] Mobile-responsive pin/unpin with long-press interactions
- [x] **UI Design System**: String brand colors (#75F8CC mint, #33373B dark, #C0F4FB light)
- [x] **Component Architecture**: Reusable UI components (Button, Card, AppCard, Header)
- [x] **Profile Components**: Abstracted ProfileHeader, AppsList, ProfileFooter for dev/prod consistency
- [x] **Profile UX**: Apps ordered by user contributions first, profile info moved to bottom
- [x] **Development Mode**: DevProfileMock component for local testing without API dependencies
- [x] **Submission UX Improvements**:
- Enhanced empty submissions state with actionable CTA (clickable + icon)
- App name autocomplete to prevent duplicate submissions
- Fixed modal styling to match String brand guidelines (mint green buttons)
- Removed duplicate title in submission form
- Submission modal accessible from both homepage and dashboard + buttons

### 🔲 Phase 5: Personal Profile Pages (NEXT)
- [ ] Email-prefix slug generation (e.g., `string.sg/lee-kh`)
Expand All @@ -68,7 +79,8 @@
| Layer | Technology | Status |
|-------|------------|--------|
| Frontend | React 19 + Vite 7 + TypeScript | ✅ |
| Styling | Tailwind CSS 4 | ✅ |
| Styling | Tailwind CSS 4 + String Design System | ✅ |
| Components | Abstracted UI Library (Button, Card, etc.) | ✅ |
| Database | NeonDB (PostgreSQL) | ✅ |
| ORM | Drizzle | ✅ |
| API | Vercel Edge Functions | ✅ |
Expand Down Expand Up @@ -101,6 +113,23 @@ string-v2/
│ ├── scrape-schools.ts # School directory scraper ✅
│ └── seed-apps.ts # Database seeder ✅
├── src/
│ ├── components/
│ │ ├── ui/
│ │ │ ├── Button.tsx # Reusable button component ✅
│ │ │ ├── Card.tsx # Reusable card component ✅
│ │ │ ├── AppCard.tsx # App display card ✅
│ │ │ ├── Header.tsx # Navigation header ✅
│ │ │ └── Modal.tsx # Modal component ✅
│ │ ├── profile/
│ │ │ ├── ProfileHeader.tsx # Profile info component ✅
│ │ │ ├── AppsList.tsx # Apps grid with sorting ✅
│ │ │ └── ProfileFooter.tsx # Branded footer ✅
│ │ ├── dashboard/
│ │ │ └── MySubmissions.tsx # User submissions tab ✅
│ │ ├── AppSubmissionForm.tsx # App submission form with autocomplete ✅
│ │ ├── DevProfileMock.tsx # Development profile mock ✅
│ │ ├── PersonalProfile.tsx # Production profile page ✅
│ │ └── UserDashboard.tsx # User dashboard ✅
│ ├── db/
│ │ ├── schema.ts # Drizzle schema (8 tables) ✅
│ │ └── index.ts # DB connection ✅
Expand All @@ -111,6 +140,7 @@ string-v2/
│ ├── App.tsx # Main landing page ✅
│ ├── main.tsx # React entry ✅
│ └── index.css # Tailwind styles ✅
├── STYLING.md # Design system documentation ✅
├── drizzle.config.ts # Drizzle config ✅
├── vite.config.ts # Vite config ✅
├── tailwind.config.js # Tailwind config ✅
Expand Down Expand Up @@ -249,11 +279,23 @@ Response: { user: {...}, apps: [...] }
POST /api/profile/apps { appId, type, isVisible }
```

#### Components to Create
#### Components Architecture ✅ COMPLETE
```typescript
// PersonalLauncherPage.tsx - Public profile view
// ProfileManagement.tsx - Inline visibility controls
// PublicPreviewToggle.tsx - WYSIWYG toggle component
// ProfileHeader.tsx - Profile info display (abstracted) ✅
// AppsList.tsx - Apps grid with contribution ordering ✅
// ProfileFooter.tsx - Branded footer with SVG logo ✅
// DevProfileMock.tsx - Development testing component ✅
// PersonalProfile.tsx - Production profile page ✅

// UI Components Library ✅
// Button.tsx, Card.tsx, AppCard.tsx, Header.tsx ✅

// Design Features ✅
- User contributions ordered first before pinned apps
- Profile info moved to bottom for content-first UX
- String dark navbar (#33373B) for better contrast
- SVG logo in footer for improved readability
- Abstracted components prevent dev/prod inconsistencies
```

#### Implementation Approach
Expand Down Expand Up @@ -288,13 +330,35 @@ mkdir extension

---

## Recent Improvements (2026-02-12)

### App Submission UX Overhaul
**Problem:** Submission flow was disconnected and modal didn't follow brand guidelines
**Solution:**
1. **Enhanced Empty State** - MySubmissions tab now shows actionable CTA with clickable + icon
2. **Autocomplete for Duplicates** - App name field queries existing apps and warns users about duplicates
3. **Brand Consistency** - Updated all modal styling to use String mint (#75F8CC) instead of generic blue
4. **Unified Access** - Submission modal accessible from both homepage + button and dashboard
5. **Cleaner UI** - Removed duplicate title, modal header now serves as the only title

**Technical Details:**
- `AppSubmissionForm.tsx`: Added autocomplete dropdown with real-time filtering
- `MySubmissions.tsx`: Enhanced empty state with interactive + button
- `App.tsx`: Added modal state and wired + button in header
- All inputs now use `focus:ring-string-mint` for consistent brand experience

---

## UGC Workflow

1. User submits app via form
2. Saved with `status: 'pending'`
3. **Submitter sees their app immediately**
4. Admin reviews via Drizzle Studio
5. Approved → visible globally
1. User clicks + icon (homepage or dashboard)
2. Modal opens with app submission form
3. User types app name → autocomplete suggests existing apps to prevent duplicates
4. If selecting existing app → yellow warning appears
5. Form validates and submits with `status: 'pending'`
6. **Submitter sees their app immediately in dashboard**
7. Admin reviews via Drizzle Studio
8. Approved → visible globally in app directory

---

Expand Down
19 changes: 17 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,23 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#1a1d1f" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="theme-color" content="#33373B" />

<!-- Favicon -->
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />

<!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/apple-touch-icon-iphone-60x60.png" />
<link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon-ipad-76x76.png" />
<link rel="apple-touch-icon" sizes="120x120" href="/apple-touch-icon-iphone-retina-120x120.png" />
<link rel="apple-touch-icon" sizes="152x152" href="/apple-touch-icon-ipad-retina-152x152.png" />

<!-- Microsoft Tile -->
<meta name="msapplication-TileColor" content="#33373B" />
<meta name="msapplication-TileImage" content="/mstile-150x150.png" />

<title>String - App Launcher for Educators</title>

<!-- Google Analytics -->
Expand Down
Loading
Loading