diff --git a/api.php b/api.php new file mode 100644 index 0000000..9d70da5 --- /dev/null +++ b/api.php @@ -0,0 +1,16 @@ +query('SELECT id, title, location, description, company, salary_range, created_at FROM jobs ORDER BY created_at DESC'); + $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC); + echo json_encode(['success' => true, 'data' => $jobs]); +} catch (PDOException $e) { + http_response_code(500); + echo json_encode(['success' => false, 'error' => 'Database error: ' . $e->getMessage()]); +} +?> \ No newline at end of file diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..82de4fa --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,74 @@ +@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;700&display=swap'); + +:root { + --primary: #3E66F8; + --secondary: #17A2B8; + --background: #F8F9FA; + --surface: #FFFFFF; + --text: #212529; + --radius: 0.5rem; +} + +body { + font-family: 'Poppins', sans-serif; + background-color: var(--background); + color: var(--text); +} + +.navbar { + background-color: var(--surface); + box-shadow: 0 2px 4px rgba(0,0,0,.05); +} + +.hero { + background: linear-gradient(45deg, var(--primary), var(--secondary)); + color: white; + padding: 4rem 1rem; + text-align: center; +} + +.job-card { + background-color: var(--surface); + border: none; + border-radius: var(--radius); + box-shadow: 0 4px 12px rgba(0,0,0,.08); + transition: all 0.3s ease; +} + +.job-card:hover { + transform: translateY(-5px); + box-shadow: 0 8px 20px rgba(0,0,0,.12); +} + +.job-card .card-title { + color: var(--primary); +} + +.job-card .badge { + background-color: rgba(62, 102, 248, 0.1); + color: var(--primary); + font-weight: 600; +} + +.btn-primary { + background-color: var(--primary); + border-color: var(--primary); + border-radius: var(--radius); + padding: 0.75rem 1.5rem; + font-weight: 600; +} + +.btn-primary:hover { + opacity: 0.9; +} + +.form-control-lg { + border-radius: var(--radius); +} + +.footer { + background-color: var(--surface); + padding: 2rem 0; + margin-top: 4rem; + border-top: 1px solid #e9ecef; +} diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..6130445 --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1 @@ +// Custom JavaScript will go here diff --git a/db/migrations/001_create_jobs_table.sql b/db/migrations/001_create_jobs_table.sql new file mode 100644 index 0000000..6fd9a9a --- /dev/null +++ b/db/migrations/001_create_jobs_table.sql @@ -0,0 +1,18 @@ +CREATE TABLE IF NOT EXISTS jobs ( + id INT AUTO_INCREMENT PRIMARY KEY, + title VARCHAR(255) NOT NULL, + company VARCHAR(255) NOT NULL, + location VARCHAR(255) NOT NULL, + description TEXT, + salary VARCHAR(100), + job_type VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Insert dummy data +INSERT INTO jobs (title, company, location, description, salary, job_type) VALUES +('Senior Frontend Developer', 'Innovate Inc.', 'San Francisco, CA', 'Join our team to build next-gen web applications with React and TypeScript.', '$120,000 - $150,000', 'Full-time'), +('UX/UI Designer', 'Creative Solutions', 'New York, NY', 'Design beautiful and intuitive interfaces for our mobile and web products.', '$90,000 - $110,000', 'Full-time'), +('Data Scientist', 'Analytics Co.', 'Remote', 'Analyze large datasets to extract meaningful insights and drive business decisions.', '$130,000 - $160,000', 'Remote'), +('Product Manager', 'AppMakers LLC', 'Austin, TX', 'Lead the product lifecycle from conception to launch for our flagship mobile app.', '$115,000 - $140,000', 'Full-time'), +('Backend Engineer (PHP)', 'Legacy Systems', 'Chicago, IL', 'Maintain and improve our existing PHP codebase, ensuring reliability and performance.', '$100,000 - $125,000', 'Contract'); diff --git a/db/setup.php b/db/setup.php new file mode 100644 index 0000000..2866340 --- /dev/null +++ b/db/setup.php @@ -0,0 +1,28 @@ + false, 'error' => 'Migration file not found.']; + } + + $sql = file_get_contents($sql_file); + $pdo->exec($sql); + + return ['success' => true, 'message' => 'Database setup and seeding completed successfully.']; + } catch (PDOException $e) { + return ['success' => false, 'error' => 'Database error: ' . $e->getMessage()]; + } +} + +// If this script is run directly from the command line, execute the migrations. +if (php_sapi_name() === 'cli') { + $result = run_migrations(); + echo $result['message'] ?? $result['error']; + echo "\n"; +} + diff --git a/index.php b/index.php index 7205f3d..0f4129b 100644 --- a/index.php +++ b/index.php @@ -1,150 +1,124 @@ - - + - - - New Style - - - - - - - - - - - - - - - - - - - + + + + Connect - Find Your Next Opportunity + + + + + + + + + + + + + + + + -
-
-

Analyzing your requirements and generating your website…

-
- Loading… -
-

AI is collecting your requirements and applying the first changes.

-

This page will update automatically as the plan is implemented.

-

Runtime: PHP — UTC

-
-
- + + + + + +
+ +
+
+

Find Your Next Opportunity

+

The AI-powered platform to discover jobs and build your career.

+
+
+
+ + +
+
+
+
+
+ + +
+
+ query('SELECT * FROM jobs ORDER BY created_at DESC'); + $jobs = $stmt->fetchAll(); + + if (empty($jobs)) { + echo "

No job listings found at the moment. Please check back later.

"; + } else { + foreach ($jobs as $job) { + echo ' +
+
+
+
' . htmlspecialchars($job['title']) . '
+
' . htmlspecialchars($job['company']) . '
+

' . htmlspecialchars($job['location']) . '

+

' . substr(htmlspecialchars($job['description']), 0, 100) . '...

+
+ ' . htmlspecialchars($job['job_type']) . ' + ' . htmlspecialchars($job['salary']) . ' +
+ +
+
+
'; + } + } + } catch (PDOException $e) { + // In a real app, log this error. For now, show a friendly message. + echo "

Error: Could not connect to the database to fetch jobs.

"; + } + ?> +
+
+
+ + + + + + + + - + \ No newline at end of file diff --git a/mobile_app/lib/main.dart b/mobile_app/lib/main.dart new file mode 100644 index 0000000..bce909d --- /dev/null +++ b/mobile_app/lib/main.dart @@ -0,0 +1,281 @@ + +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; + +import 'src/calendar_screen.dart'; +import 'src/education_screen.dart'; +import 'src/resume_screen.dart'; +import 'src/settings_screen.dart'; + +void main() { + runApp(const MyApp()); +} + +// Color Palette +const Color darkJungleGreen = Color(0xFF000505); +const Color englishViolet = Color(0xFF3B3355); +const Color slateGray = Color(0xFF5D5D81); +const Color lightSteelBlue = Color(0xFFBFCDE0); +const Color ghostWhite = Color(0xFFFEFCFD); + +class MyApp extends StatefulWidget { + const MyApp({Key? key}) : super(key: key); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + bool _isDarkMode = false; + + void _toggleTheme(bool isDark) { + setState(() { + _isDarkMode = isDark; + }); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Connect', + theme: ThemeData( + brightness: Brightness.light, + primaryColor: englishViolet, + scaffoldBackgroundColor: ghostWhite, + colorScheme: const ColorScheme.light( + primary: englishViolet, + secondary: slateGray, + surface: ghostWhite, + background: ghostWhite, + onPrimary: ghostWhite, + onSecondary: darkJungleGreen, + onSurface: darkJungleGreen, + onBackground: darkJungleGreen, + error: Colors.red, + onError: ghostWhite, + ), + appBarTheme: const AppBarTheme( + backgroundColor: englishViolet, + foregroundColor: ghostWhite, + titleTextStyle: TextStyle(fontFamily: 'Poppins', fontSize: 20, fontWeight: FontWeight.bold), + ), + textTheme: const TextTheme( + bodyLarge: TextStyle(color: darkJungleGreen), + titleLarge: TextStyle(color: darkJungleGreen, fontWeight: FontWeight.bold), + ), + floatingActionButtonTheme: const FloatingActionButtonThemeData( + backgroundColor: englishViolet, + foregroundColor: ghostWhite, + ), + ), + darkTheme: ThemeData( + brightness: Brightness.dark, + primaryColor: lightSteelBlue, + scaffoldBackgroundColor: darkJungleGreen, + colorScheme: const ColorScheme.dark( + primary: lightSteelBlue, + secondary: slateGray, + surface: darkJungleGreen, + background: darkJungleGreen, + onPrimary: darkJungleGreen, + onSecondary: ghostWhite, + onSurface: ghostWhite, + onBackground: ghostWhite, + error: Colors.red, + onError: ghostWhite, + ), + appBarTheme: const AppBarTheme( + backgroundColor: slateGray, + foregroundColor: ghostWhite, + titleTextStyle: TextStyle(fontFamily: 'Poppins', fontSize: 20, fontWeight: FontWeight.bold), + ), + textTheme: const TextTheme( + bodyLarge: TextStyle(color: ghostWhite), + titleLarge: TextStyle(color: ghostWhite, fontWeight: FontWeight.bold), + ), + floatingActionButtonTheme: const FloatingActionButtonThemeData( + backgroundColor: lightSteelBlue, + foregroundColor: darkJungleGreen, + ), + ), + themeMode: _isDarkMode ? ThemeMode.dark : ThemeMode.light, + home: JobListingScreen(isDarkMode: _isDarkMode, onThemeChanged: _toggleTheme), + ); + } +} + +class JobListingScreen extends StatefulWidget { + final bool isDarkMode; + final ValueChanged onThemeChanged; + + const JobListingScreen({ + Key? key, + required this.isDarkMode, + required this.onThemeChanged, + }) : super(key: key); + + @override + _JobListingScreenState createState() => _JobListingScreenState(); +} + +class _JobListingScreenState extends State { + List jobs = []; + bool isLoading = true; + String? error; + + @override + void initState() { + super.initState(); + fetchJobs(); + } + + Future fetchJobs() async { + // IMPORTANT: Replace with your actual IP/domain in a real environment. + // 10.0.2.2 is the special alias for the host machine's localhost in the Android emulator. + const String apiUrl = 'http://10.0.2.2/api.php'; + try { + final response = await http.get(Uri.parse(apiUrl)); + if (response.statusCode == 200) { + final result = json.decode(response.body); + if (result['success'] == true) { + setState(() { + jobs = result['data']; + isLoading = false; + }); + } else { + setState(() { + error = result['error'] ?? 'An unknown error occurred.'; + isLoading = false; + }); + } + } else { + setState(() { + error = 'Failed to load jobs. Status code: ${response.statusCode}'; + isLoading = false; + }); + } + } catch (e) { + setState(() { + error = 'Failed to connect to the server. Please check your network connection and the API endpoint. Details: $e'; + isLoading = false; + }); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Connect'), + ), + drawer: Drawer( // Vertical navigation drawer + child: ListView( + padding: EdgeInsets.zero, + children: [ + const DrawerHeader( + decoration: BoxDecoration( + color: englishViolet, + ), + child: Text( + 'Connect', + style: TextStyle(color: Colors.white, fontSize: 24), + ), + ), + ListTile( + leading: const Icon(Icons.school), + title: const Text('Education/Information'), + onTap: () { + Navigator.pop(context); + Navigator.push(context, MaterialPageRoute(builder: (context) => const EducationScreen())); + }, + ), + ListTile( + leading: const Icon(Icons.description), + title: const Text('Resume/Job Letter'), + onTap: () { + Navigator.pop(context); + Navigator.push(context, MaterialPageRoute(builder: (context) => const ResumeScreen())); + }, + ), + ListTile( + leading: const Icon(Icons.calendar_today), + title: const Text('Calendar'), + onTap: () { + Navigator.pop(context); + Navigator.push(context, MaterialPageRoute(builder: (context) => const CalendarScreen())); + }, + ), + ListTile( + leading: const Icon(Icons.settings), + title: const Text('Settings'), + onTap: () { + Navigator.pop(context); + Navigator.push(context, MaterialPageRoute(builder: (context) => SettingsScreen( + isDarkMode: widget.isDarkMode, + onThemeChanged: widget.onThemeChanged, + ))); + }, + ), + ], + ), + ), + body: buildBody(), + floatingActionButton: FloatingActionButton( + onPressed: () { + // AI Chatbot action + }, + child: const Icon(Icons.chat), + ), + ); + } + + Widget buildBody() { + if (isLoading) { + return const Center(child: CircularProgressIndicator()); + } + + if (error != null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + 'Error: $error', + textAlign: TextAlign.center, + style: const TextStyle(color: Colors.red, fontSize: 16), + ), + ), + ); + } + + if (jobs.isEmpty) { + return const Center(child: Text('No jobs found.')); + } + + return ListView.builder( + itemCount: jobs.length, + itemBuilder: (context, index) { + final job = jobs[index]; + return Card( + margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + elevation: 4, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + child: ListTile( + title: Text(job['title'] ?? 'No Title', style: Theme.of(context).textTheme.titleLarge), + subtitle: Text( + '${job['company'] ?? 'No Company'} - ${job['location'] ?? 'No Location'}', + style: Theme.of(context).textTheme.bodyLarge, + ), + trailing: Text( + job['salary_range'] ?? '', + style: TextStyle(color: Theme.of(context).colorScheme.primary), + ), + onTap: () { + // Show job details + }, + ), + ); + }, + ); + } +} diff --git a/mobile_app/lib/src/calendar_screen.dart b/mobile_app/lib/src/calendar_screen.dart new file mode 100644 index 0000000..bf4424f --- /dev/null +++ b/mobile_app/lib/src/calendar_screen.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; + +class CalendarScreen extends StatelessWidget { + const CalendarScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Calendar'), + ), + body: const Center( + child: Text('Calendar Screen'), + ), + ); + } +} diff --git a/mobile_app/lib/src/education_screen.dart b/mobile_app/lib/src/education_screen.dart new file mode 100644 index 0000000..1f33371 --- /dev/null +++ b/mobile_app/lib/src/education_screen.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; + +class EducationScreen extends StatelessWidget { + const EducationScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Education/Information'), + ), + body: const Center( + child: Text('Education/Information Screen'), + ), + ); + } +} diff --git a/mobile_app/lib/src/resume_screen.dart b/mobile_app/lib/src/resume_screen.dart new file mode 100644 index 0000000..abde44d --- /dev/null +++ b/mobile_app/lib/src/resume_screen.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; + +class ResumeScreen extends StatelessWidget { + const ResumeScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Resume/Job Letter'), + ), + body: const Center( + child: Text('Resume/Job Letter Screen'), + ), + ); + } +} diff --git a/mobile_app/lib/src/settings_screen.dart b/mobile_app/lib/src/settings_screen.dart new file mode 100644 index 0000000..b1e941e --- /dev/null +++ b/mobile_app/lib/src/settings_screen.dart @@ -0,0 +1,179 @@ +import 'package:flutter/material.dart'; + +class SettingsScreen extends StatefulWidget { + final bool isDarkMode; + final ValueChanged onThemeChanged; + + const SettingsScreen({ + super.key, + required this.isDarkMode, + required this.onThemeChanged, + }); + + @override + State createState() => _SettingsScreenState(); +} + +class _SettingsScreenState extends State { + bool _pushNotifications = true; + bool _emailNotifications = true; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Settings'), + elevation: 0, + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + foregroundColor: Theme.of(context).textTheme.bodyLarge?.color, + ), + body: ListView( + children: [ + _buildSectionHeader('Account'), + _buildSettingsTile( + context, + icon: Icons.person_outline, + title: 'Edit Profile', + onTap: () { + // Placeholder for navigation + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Navigate to Edit Profile')), + ); + }, + ), + _buildSettingsTile( + context, + icon: Icons.lock_outline, + title: 'Change Password', + onTap: () { + // Placeholder for navigation + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Navigate to Change Password')), + ); + }, + ), + const Divider(), + _buildSectionHeader('Notifications'), + _buildSwitchTile( + context, + title: 'Push Notifications', + value: _pushNotifications, + onChanged: (value) { + setState(() { + _pushNotifications = value; + }); + }, + ), + _buildSwitchTile( + context, + title: 'Email Notifications', + value: _emailNotifications, + onChanged: (value) { + setState(() { + _emailNotifications = value; + }); + }, + ), + const Divider(), + _buildSectionHeader('Appearance'), + _buildSwitchTile( + context, + title: 'Dark Mode', + value: widget.isDarkMode, + onChanged: widget.onThemeChanged, + ), + const Divider(), + _buildSectionHeader('Support'), + _buildSettingsTile( + context, + icon: Icons.help_outline, + title: 'Help Center', + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Navigate to Help Center')), + ); + }, + ), + _buildSettingsTile( + context, + icon: Icons.contact_support_outlined, + title: 'Contact Us', + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Navigate to Contact Us')), + ); + }, + ), + const Divider(), + _buildSectionHeader('Legal'), + _buildSettingsTile( + context, + icon: Icons.description_outlined, + title: 'Terms of Service', + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Navigate to Terms of Service')), + ); + }, + ), + _buildSettingsTile( + context, + icon: Icons.privacy_tip_outlined, + title: 'Privacy Policy', + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Navigate to Privacy Policy')), + ); + }, + ), + const Divider(), + ListTile( + title: Text( + 'Logout', + style: TextStyle(color: Theme.of(context).colorScheme.error), + ), + leading: Icon(Icons.logout, color: Theme.of(context).colorScheme.error), + onTap: () { + // Placeholder for logout logic + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('User logged out')), + ); + }, + ), + ], + ), + ); + } + + Widget _buildSectionHeader(String title) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + child: Text( + title.toUpperCase(), + style: TextStyle( + color: Theme.of(context).textTheme.bodySmall?.color, + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ), + ); + } + + Widget _buildSettingsTile(BuildContext context, {required IconData icon, required String title, required VoidCallback onTap}) { + return ListTile( + leading: Icon(icon, color: Theme.of(context).colorScheme.secondary), + title: Text(title), + trailing: const Icon(Icons.chevron_right), + onTap: onTap, + ); + } + + Widget _buildSwitchTile(BuildContext context, {required String title, required bool value, required ValueChanged onChanged}) { + return SwitchListTile( + title: Text(title), + value: value, + onChanged: onChanged, + activeColor: Theme.of(context).colorScheme.primary, + ); + } +} \ No newline at end of file diff --git a/mobile_app/pubspec.yaml b/mobile_app/pubspec.yaml new file mode 100644 index 0000000..2ac7e1c --- /dev/null +++ b/mobile_app/pubspec.yaml @@ -0,0 +1,23 @@ +name: connect_app +description: A new Flutter project. + +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +version: 1.0.0+1 + +environment: + sdk: '>=2.19.0 <3.0.0' + +dependencies: + flutter: + sdk: flutter + http: ^0.13.5 + cupertino_icons: ^1.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +flutter: + uses-material-design: true