diff --git a/.claude/commands/merge-to-main.md b/.claude/commands/merge-to-main.md new file mode 100644 index 0000000..2cde4ec --- /dev/null +++ b/.claude/commands/merge-to-main.md @@ -0,0 +1,100 @@ +# Merge to Main + +Review the current code changes and create a comprehensive commit message, then merge to main. + +## Workflow + +### Step 1: Analyze Current State + +First, determine the current git state: +1. Check if on a feature branch or main +2. Identify all changes: + - `git status` - Check for staged/unstaged changes + - `git log main..HEAD --oneline` - If on a branch, see commits ahead of main + - `git diff main...HEAD --stat` - Summary of all changes vs main + +### Step 2: Review the Code Changes + +Thoroughly review all changes to understand what was done: +1. Read through the diff: `git diff main...HEAD` (or `git diff` if uncommitted changes exist) +2. Look at changed files and understand the context +3. Identify: + - New features added + - Bugs fixed + - Refactoring done + - Tests added/modified + - Configuration changes + +### Step 3: Generate Comprehensive Commit Message + +Create a detailed commit message following this format: + +``` +(): + + + +Changes: +- +- +- ... + + +``` + +**Types**: feat, fix, refactor, test, docs, chore, perf, style +**Scope**: The area of the codebase (e.g., user-profile, auth, api) + +The summary should be: +- Under 72 characters +- In imperative mood ("add" not "added") +- Descriptive of the overall change + +The description should: +- Explain the "why" behind the changes +- Reference any related issues (bd issue IDs if applicable) +- List all significant changes made + +### Step 4: Present to User + +Show the user: +1. Summary of all changes (files changed, insertions, deletions) +2. The proposed commit message +3. Ask for confirmation or modifications + +### Step 5: Execute Merge + +Based on the current state, offer appropriate options: + +**If on a feature branch with commits:** +1. Option A: Squash merge to main (recommended for cleaner history) + ``` + git checkout main + git merge --squash + git commit -m "" + ``` +2. Option B: Regular merge to main + ``` + git checkout main + git merge + ``` + +**If on a feature branch with uncommitted changes:** +1. First commit the changes with the comprehensive message +2. Then offer merge options as above + +**If on main with uncommitted changes:** +1. Commit directly with the comprehensive message + +### Step 6: Cleanup (Optional) + +After successful merge, offer to: +- Delete the feature branch locally: `git branch -d ` +- Delete the feature branch remotely: `git push origin --delete ` + +## Important Notes + +- Always show the user what will happen before executing +- Never force push or use destructive operations without explicit confirmation +- If there are merge conflicts, stop and help the user resolve them +- Preserve the Co-Authored-By trailer as required by the repo guidelines diff --git a/assets/icons/bluesky_butterfly.svg b/assets/icons/bluesky_butterfly.svg new file mode 100644 index 0000000..7a97aeb --- /dev/null +++ b/assets/icons/bluesky_butterfly.svg @@ -0,0 +1 @@ + diff --git a/lib/constants/bluesky_colors.dart b/lib/constants/bluesky_colors.dart index 3bca77d..1602ac1 100644 --- a/lib/constants/bluesky_colors.dart +++ b/lib/constants/bluesky_colors.dart @@ -33,6 +33,9 @@ class BlueskyColors { /// Link color - Bluesky blue static const linkBlue = Color(0xFF1185FE); + /// Link color lightened - for press states + static const linkBlueLight = Color(0xFF4AABFF); + /// Avatar fallback background static const avatarFallback = Color(0xFF2E3D4F); diff --git a/lib/screens/landing_screen.dart b/lib/screens/landing_screen.dart index 4e6c15b..70dec0a 100644 --- a/lib/screens/landing_screen.dart +++ b/lib/screens/landing_screen.dart @@ -7,6 +7,7 @@ import 'package:google_fonts/google_fonts.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import '../constants/app_colors.dart'; +import '../widgets/bluesky_sign_in_button.dart'; import '../widgets/primary_button.dart'; /// Landing screen with Coves beach-inspired dark theme design. @@ -202,6 +203,13 @@ class LandingScreen extends StatelessWidget { const SizedBox(height: 14), + // Sign in with Bluesky button + BlueskySignInButton( + onPressed: () => context.push('/login'), + ), + + const SizedBox(height: 14), + // Create account button PrimaryButton( title: 'Create account', diff --git a/lib/widgets/bluesky_sign_in_button.dart b/lib/widgets/bluesky_sign_in_button.dart new file mode 100644 index 0000000..d1c9034 --- /dev/null +++ b/lib/widgets/bluesky_sign_in_button.dart @@ -0,0 +1,150 @@ +import 'dart:async' show unawaited; + +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; + +import '../constants/bluesky_colors.dart'; + +/// Animation duration for press state transitions. +const _kPressAnimationDuration = Duration(milliseconds: 150); + +/// Sign in with Bluesky button component. +/// +/// A branded outline button for Bluesky authentication that integrates +/// seamlessly with the Coves design system while maintaining Bluesky's +/// brand identity. +/// +/// Features: +/// - Transparent background with Bluesky blue border +/// - Official butterfly logo in Bluesky blue +/// - Smooth press state animations +/// - Disabled state support +/// - Consistent with Coves button sizing (75% width, 52px height) +class BlueskySignInButton extends StatefulWidget { + const BlueskySignInButton({ + super.key, + required this.onPressed, + this.disabled = false, + this.title = 'Sign in with Bluesky', + }); + + final VoidCallback onPressed; + final bool disabled; + final String title; + + @override + State createState() => _BlueskySignInButtonState(); +} + +class _BlueskySignInButtonState extends State { + bool _isPressed = false; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: MediaQuery.of(context).size.width * 0.75, + height: 52, + child: GestureDetector( + onTapDown: (_) { + if (!widget.disabled) { + setState(() => _isPressed = true); + } + }, + onTapUp: (_) { + if (_isPressed) { + setState(() => _isPressed = false); + widget.onPressed(); + } + }, + onTapCancel: () { + if (_isPressed) { + setState(() => _isPressed = false); + } + }, + child: AnimatedContainer( + duration: _kPressAnimationDuration, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100), + color: _getBackgroundColor(), + border: Border.all( + color: _getBorderColor(), + width: 2, + ), + ), + child: Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + // Official Bluesky butterfly logo + SvgPicture.asset( + 'assets/icons/bluesky_butterfly.svg', + width: 22, + height: 22, + colorFilter: ColorFilter.mode( + _getContentColor(), + BlendMode.srcIn, + ), + errorBuilder: (context, error, stackTrace) { + unawaited( + Sentry.captureException( + error, + stackTrace: stackTrace, + withScope: (scope) { + scope + ..setTag('asset', 'bluesky_butterfly.svg') + ..setTag('widget', 'BlueskySignInButton'); + }, + ), + ); + return Icon( + Icons.cloud_outlined, + size: 22, + color: _getContentColor(), + ); + }, + ), + const SizedBox(width: 12), + Text( + widget.title, + style: GoogleFonts.nunito( + fontSize: 16, + fontWeight: FontWeight.w700, + color: _getContentColor(), + letterSpacing: 0.3, + ), + ), + ], + ), + ), + ), + ), + ); + } + + Color _getBackgroundColor() { + if (_isPressed) { + return BlueskyColors.linkBlue.withValues(alpha: 0.1); + } + return Colors.transparent; + } + + Color _getBorderColor() { + if (widget.disabled) { + return BlueskyColors.linkBlue.withValues(alpha: 0.3); + } + if (_isPressed) { + return BlueskyColors.linkBlueLight; + } + return BlueskyColors.linkBlue; + } + + Color _getContentColor() { + if (widget.disabled) { + return BlueskyColors.linkBlue.withValues(alpha: 0.5); + } + return BlueskyColors.linkBlue; + } +}