commit
d581229145
@ -0,0 +1,3 @@ |
|||||||
|
{ |
||||||
|
"template": "bolt-vite-react-ts" |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
For all designs I ask you to make, have them be beautiful, not cookie cutter. Make webpages that are fully featured and worthy for production. |
||||||
|
|
||||||
|
By default, this template supports JSX syntax with Tailwind CSS classes, React hooks, and Lucide React for icons. Do not install other packages for UI themes, icons, etc unless absolutely necessary or I request them. |
||||||
|
|
||||||
|
Use icons from lucide-react for logos. |
||||||
|
|
||||||
|
Use stock photos from unsplash where appropriate, only valid URLs you know exist. Do not download the images, only link to them in image tags. |
||||||
|
|
@ -0,0 +1,13 @@ |
|||||||
|
# MongoDB Atlas Configuration |
||||||
|
MONGODB_URI=mongodb+srv://username:password@cluster0.mongodb.net/ecoconnect?retryWrites=true&w=majority |
||||||
|
|
||||||
|
# Session Configuration |
||||||
|
SESSION_SECRET=your-secret-key-here |
||||||
|
|
||||||
|
# Server Configuration |
||||||
|
PORT=5000 |
||||||
|
|
||||||
|
# Client URL |
||||||
|
CLIENT_URL=http://localhost:5173 |
||||||
|
VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InNmY3FlYnd6bXducG1lcXZxYnNuIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzY3MzY4NzgsImV4cCI6MjA1MjMxMjg3OH0._il8wrky1N26ef4FZJtXa3DPcowbsb1ENBlcJYu3N8g |
||||||
|
VITE_SUPABASE_URL=https://sfcqebwzmwnpmeqvqbsn.supabase.co |
@ -0,0 +1,24 @@ |
|||||||
|
# Logs |
||||||
|
logs |
||||||
|
*.log |
||||||
|
npm-debug.log* |
||||||
|
yarn-debug.log* |
||||||
|
yarn-error.log* |
||||||
|
pnpm-debug.log* |
||||||
|
lerna-debug.log* |
||||||
|
|
||||||
|
node_modules |
||||||
|
dist |
||||||
|
dist-ssr |
||||||
|
*.local |
||||||
|
|
||||||
|
# Editor directories and files |
||||||
|
.vscode/* |
||||||
|
!.vscode/extensions.json |
||||||
|
.idea |
||||||
|
.DS_Store |
||||||
|
*.suo |
||||||
|
*.ntvs* |
||||||
|
*.njsproj |
||||||
|
*.sln |
||||||
|
*.sw? |
@ -0,0 +1,28 @@ |
|||||||
|
import js from '@eslint/js'; |
||||||
|
import globals from 'globals'; |
||||||
|
import reactHooks from 'eslint-plugin-react-hooks'; |
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh'; |
||||||
|
import tseslint from 'typescript-eslint'; |
||||||
|
|
||||||
|
export default tseslint.config( |
||||||
|
{ ignores: ['dist'] }, |
||||||
|
{ |
||||||
|
extends: [js.configs.recommended, ...tseslint.configs.recommended], |
||||||
|
files: ['**/*.{ts,tsx}'], |
||||||
|
languageOptions: { |
||||||
|
ecmaVersion: 2020, |
||||||
|
globals: globals.browser, |
||||||
|
}, |
||||||
|
plugins: { |
||||||
|
'react-hooks': reactHooks, |
||||||
|
'react-refresh': reactRefresh, |
||||||
|
}, |
||||||
|
rules: { |
||||||
|
...reactHooks.configs.recommended.rules, |
||||||
|
'react-refresh/only-export-components': [ |
||||||
|
'warn', |
||||||
|
{ allowConstantExport: true }, |
||||||
|
], |
||||||
|
}, |
||||||
|
} |
||||||
|
); |
@ -0,0 +1,13 @@ |
|||||||
|
<!doctype html> |
||||||
|
<html lang="en"> |
||||||
|
<head> |
||||||
|
<meta charset="UTF-8" /> |
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||||
|
<title>Vite + React + TS</title> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div id="root"></div> |
||||||
|
<script type="module" src="/src/main.tsx"></script> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,9 @@ |
|||||||
|
[build] |
||||||
|
command = "npm run build" |
||||||
|
publish = "dist" |
||||||
|
|
||||||
|
[[redirects]] |
||||||
|
from = "/*" |
||||||
|
to = "/index.html" |
||||||
|
status = 200 |
||||||
|
force = true |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,31 @@ |
|||||||
|
{ |
||||||
|
"name": "ecoconnect", |
||||||
|
"private": true, |
||||||
|
"version": "1.0.0", |
||||||
|
"type": "module", |
||||||
|
"scripts": { |
||||||
|
"dev": "vite", |
||||||
|
"build": "vite build", |
||||||
|
"lint": "eslint .", |
||||||
|
"preview": "vite preview", |
||||||
|
"server": "node server/index.js", |
||||||
|
"dev:all": "concurrently \"npm run dev\" \"npm run server\"" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"axios": "^1.6.7", |
||||||
|
"bootstrap": "^5.3.3", |
||||||
|
"lucide-react": "^0.344.0", |
||||||
|
"react": "^18.3.1", |
||||||
|
"react-dom": "^18.3.1", |
||||||
|
"react-router-dom": "^6.22.3" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@types/react": "^18.3.5", |
||||||
|
"@types/react-dom": "^18.3.0", |
||||||
|
"@vitejs/plugin-react": "^4.3.1", |
||||||
|
"autoprefixer": "^10.4.18", |
||||||
|
"eslint": "^9.9.1", |
||||||
|
"typescript": "^5.5.3", |
||||||
|
"vite": "^6.0.7" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,5 @@ |
|||||||
|
export default { |
||||||
|
plugins: { |
||||||
|
autoprefixer: {} |
||||||
|
}, |
||||||
|
}; |
After Width: | Height: | Size: 24 KiB |
@ -0,0 +1,49 @@ |
|||||||
|
import passport from 'passport'; |
||||||
|
import { Strategy as GoogleStrategy } from 'passport-google-oauth20'; |
||||||
|
import dotenv from 'dotenv'; |
||||||
|
import User from '../models/User.js'; |
||||||
|
|
||||||
|
dotenv.config(); |
||||||
|
|
||||||
|
passport.serializeUser((user, done) => { |
||||||
|
done(null, user.id); |
||||||
|
}); |
||||||
|
|
||||||
|
passport.deserializeUser(async (id, done) => { |
||||||
|
try { |
||||||
|
const user = await User.findById(id); |
||||||
|
done(null, user); |
||||||
|
} catch (err) { |
||||||
|
done(err, null); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
passport.use( |
||||||
|
new GoogleStrategy( |
||||||
|
{ |
||||||
|
clientID: process.env.GOOGLE_CLIENT_ID, |
||||||
|
clientSecret: process.env.GOOGLE_CLIENT_SECRET, |
||||||
|
callbackURL: 'http://localhost:5000/auth/google/callback', |
||||||
|
proxy: true |
||||||
|
}, |
||||||
|
async (accessToken, refreshToken, profile, done) => { |
||||||
|
try { |
||||||
|
const existingUser = await User.findOne({ googleId: profile.id }); |
||||||
|
if (existingUser) { |
||||||
|
return done(null, existingUser); |
||||||
|
} |
||||||
|
|
||||||
|
const newUser = await new User({ |
||||||
|
googleId: profile.id, |
||||||
|
email: profile.emails[0].value, |
||||||
|
name: profile.displayName, |
||||||
|
avatar: profile.photos[0].value, |
||||||
|
}).save(); |
||||||
|
|
||||||
|
done(null, newUser); |
||||||
|
} catch (err) { |
||||||
|
done(err, null); |
||||||
|
} |
||||||
|
} |
||||||
|
) |
||||||
|
); |
@ -0,0 +1,85 @@ |
|||||||
|
import express from 'express'; |
||||||
|
import cors from 'cors'; |
||||||
|
import dotenv from 'dotenv'; |
||||||
|
import mongoose from 'mongoose'; |
||||||
|
import session from 'express-session'; |
||||||
|
import MongoStore from 'connect-mongo'; |
||||||
|
import authRoutes from './routes/auth.js'; |
||||||
|
import volunteerRoutes from './routes/volunteer.js'; |
||||||
|
|
||||||
|
dotenv.config(); |
||||||
|
|
||||||
|
const app = express(); |
||||||
|
|
||||||
|
// CORS configuration
|
||||||
|
app.use(cors({ |
||||||
|
origin: ['http://localhost:5173', 'http://127.0.0.1:5173'], |
||||||
|
credentials: true, |
||||||
|
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], |
||||||
|
allowedHeaders: ['Content-Type', 'Accept', 'Authorization'], |
||||||
|
})); |
||||||
|
|
||||||
|
app.use(express.json()); |
||||||
|
|
||||||
|
// Connect to MongoDB Atlas
|
||||||
|
mongoose.connect(process.env.MONGODB_URI) |
||||||
|
.then(() => { |
||||||
|
console.log('Connected to MongoDB Atlas successfully'); |
||||||
|
}) |
||||||
|
.catch((err) => { |
||||||
|
console.error('MongoDB Atlas connection error:', err); |
||||||
|
process.exit(1); |
||||||
|
}); |
||||||
|
|
||||||
|
// Session configuration with MongoDB Atlas store
|
||||||
|
app.use(session({ |
||||||
|
secret: process.env.SESSION_SECRET || 'your-secret-key', |
||||||
|
resave: false, |
||||||
|
saveUninitialized: false, |
||||||
|
store: MongoStore.create({ |
||||||
|
mongoUrl: process.env.MONGODB_URI, |
||||||
|
ttl: 24 * 60 * 60, // 1 day
|
||||||
|
}), |
||||||
|
cookie: { |
||||||
|
secure: process.env.NODE_ENV === 'production', |
||||||
|
sameSite: 'lax', |
||||||
|
maxAge: 24 * 60 * 60 * 1000, // 1 day
|
||||||
|
httpOnly: true, |
||||||
|
}, |
||||||
|
})); |
||||||
|
|
||||||
|
// Middleware to attach user to request
|
||||||
|
app.use(async (req, res, next) => { |
||||||
|
if (req.session.userId) { |
||||||
|
try { |
||||||
|
const user = await mongoose.model('User').findById(req.session.userId); |
||||||
|
req.user = user; |
||||||
|
} catch (error) { |
||||||
|
console.error('Error fetching user:', error); |
||||||
|
} |
||||||
|
} |
||||||
|
next(); |
||||||
|
}); |
||||||
|
|
||||||
|
// Mount routes
|
||||||
|
app.use('/auth', authRoutes); |
||||||
|
app.use('/volunteer', volunteerRoutes); |
||||||
|
|
||||||
|
// Error handling middleware
|
||||||
|
app.use((err, req, res, next) => { |
||||||
|
console.error('Error:', err); |
||||||
|
res.status(500).json({
|
||||||
|
error: 'Something went wrong!', |
||||||
|
message: process.env.NODE_ENV === 'development' ? err.message : undefined |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
const PORT = process.env.PORT || 5000; |
||||||
|
|
||||||
|
app.listen(PORT, () => { |
||||||
|
console.log(`Server is running on port ${PORT}`); |
||||||
|
}); |
||||||
|
|
||||||
|
mongoose.connection.on('error', (err) => { |
||||||
|
console.error('MongoDB connection error:', err); |
||||||
|
}); |
@ -0,0 +1,16 @@ |
|||||||
|
export const requireAuth = (req, res, next) => { |
||||||
|
if (!req.user) { |
||||||
|
return res.status(401).json({ error: 'You must be logged in.' }); |
||||||
|
} |
||||||
|
next(); |
||||||
|
}; |
||||||
|
|
||||||
|
export const requireAdmin = (req, res, next) => { |
||||||
|
if (!req.user) { |
||||||
|
return res.status(401).json({ error: 'You must be logged in.' }); |
||||||
|
} |
||||||
|
if (req.user.role !== 'admin') { |
||||||
|
return res.status(403).json({ error: 'Access denied. Admin privileges required.' }); |
||||||
|
} |
||||||
|
next(); |
||||||
|
}; |
@ -0,0 +1,34 @@ |
|||||||
|
import mongoose from 'mongoose'; |
||||||
|
import bcrypt from 'bcryptjs'; |
||||||
|
|
||||||
|
const userSchema = new mongoose.Schema({ |
||||||
|
email: { |
||||||
|
type: String, |
||||||
|
required: true, |
||||||
|
unique: true, |
||||||
|
}, |
||||||
|
password: { |
||||||
|
type: String, |
||||||
|
required: true |
||||||
|
}, |
||||||
|
name: String, |
||||||
|
role: { |
||||||
|
type: String, |
||||||
|
enum: ['user', 'admin'], |
||||||
|
default: 'user' |
||||||
|
}, |
||||||
|
createdAt: { |
||||||
|
type: Date, |
||||||
|
default: Date.now, |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
// Hash password before saving
|
||||||
|
userSchema.pre('save', async function(next) { |
||||||
|
if (this.isModified('password')) { |
||||||
|
this.password = await bcrypt.hash(this.password, 12); |
||||||
|
} |
||||||
|
next(); |
||||||
|
}); |
||||||
|
|
||||||
|
export default mongoose.model('User', userSchema); |
@ -0,0 +1,39 @@ |
|||||||
|
import mongoose from 'mongoose'; |
||||||
|
|
||||||
|
const volunteerSchema = new mongoose.Schema({ |
||||||
|
fullName: { |
||||||
|
type: String, |
||||||
|
required: true |
||||||
|
}, |
||||||
|
address: { |
||||||
|
type: String, |
||||||
|
required: true |
||||||
|
}, |
||||||
|
phone: { |
||||||
|
type: String, |
||||||
|
required: true |
||||||
|
}, |
||||||
|
email: { |
||||||
|
type: String, |
||||||
|
required: true, |
||||||
|
unique: true |
||||||
|
}, |
||||||
|
organization: { |
||||||
|
type: String, |
||||||
|
default: '' |
||||||
|
}, |
||||||
|
interests: [{ |
||||||
|
type: String, |
||||||
|
enum: ['environment', 'education', 'community', 'events'] |
||||||
|
}], |
||||||
|
availability: [{ |
||||||
|
type: String, |
||||||
|
enum: ['weekdays', 'weekends', 'evenings'] |
||||||
|
}], |
||||||
|
createdAt: { |
||||||
|
type: Date, |
||||||
|
default: Date.now |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
export default mongoose.model('Volunteer', volunteerSchema); |
@ -0,0 +1,104 @@ |
|||||||
|
import express from 'express'; |
||||||
|
import bcrypt from 'bcryptjs'; |
||||||
|
import User from '../models/User.js'; |
||||||
|
|
||||||
|
const router = express.Router(); |
||||||
|
|
||||||
|
// Register route
|
||||||
|
router.post('/register', async (req, res) => { |
||||||
|
try { |
||||||
|
const { email, password, name } = req.body; |
||||||
|
|
||||||
|
// Check if user exists
|
||||||
|
const existingUser = await User.findOne({ email }); |
||||||
|
if (existingUser) { |
||||||
|
return res.status(400).json({ message: 'User already exists' }); |
||||||
|
} |
||||||
|
|
||||||
|
// Create new user
|
||||||
|
const user = new User({ |
||||||
|
email, |
||||||
|
password, |
||||||
|
name |
||||||
|
}); |
||||||
|
|
||||||
|
await user.save(); |
||||||
|
|
||||||
|
res.status(201).json({ message: 'Registration successful' }); |
||||||
|
} catch (error) { |
||||||
|
console.error('Registration error:', error); |
||||||
|
res.status(500).json({ message: 'Server error' }); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
// Login route
|
||||||
|
router.post('/login', async (req, res) => { |
||||||
|
try { |
||||||
|
const { email, password } = req.body; |
||||||
|
|
||||||
|
// Find user
|
||||||
|
const user = await User.findOne({ email }); |
||||||
|
if (!user) { |
||||||
|
return res.status(401).json({ message: 'Invalid email or password' }); |
||||||
|
} |
||||||
|
|
||||||
|
// Check password
|
||||||
|
const isMatch = await bcrypt.compare(password, user.password); |
||||||
|
if (!isMatch) { |
||||||
|
return res.status(401).json({ message: 'Invalid email or password' }); |
||||||
|
} |
||||||
|
|
||||||
|
// Create session
|
||||||
|
req.session.userId = user._id; |
||||||
|
|
||||||
|
res.json({ |
||||||
|
message: 'Login successful', |
||||||
|
user: { |
||||||
|
id: user._id, |
||||||
|
name: user.name, |
||||||
|
email: user.email, |
||||||
|
role: user.role |
||||||
|
} |
||||||
|
}); |
||||||
|
} catch (error) { |
||||||
|
console.error('Login error:', error); |
||||||
|
res.status(500).json({ message: 'Server error' }); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
// Get current user
|
||||||
|
router.get('/current_user', async (req, res) => { |
||||||
|
try { |
||||||
|
if (!req.session.userId) { |
||||||
|
return res.status(401).json({ message: 'Not authenticated' }); |
||||||
|
} |
||||||
|
|
||||||
|
const user = await User.findById(req.session.userId); |
||||||
|
if (!user) { |
||||||
|
return res.status(401).json({ message: 'User not found' }); |
||||||
|
} |
||||||
|
|
||||||
|
res.json({ |
||||||
|
id: user._id, |
||||||
|
name: user.name, |
||||||
|
email: user.email, |
||||||
|
role: user.role |
||||||
|
}); |
||||||
|
} catch (error) { |
||||||
|
console.error('Error getting current user:', error); |
||||||
|
res.status(500).json({ message: 'Server error' }); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
// Logout route
|
||||||
|
router.post('/logout', (req, res) => { |
||||||
|
req.session.destroy((err) => { |
||||||
|
if (err) { |
||||||
|
console.error('Logout error:', err); |
||||||
|
return res.status(500).json({ message: 'Error logging out' }); |
||||||
|
} |
||||||
|
res.json({ message: 'Logged out successfully' }); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
export default router; |
@ -0,0 +1,40 @@ |
|||||||
|
import express from 'express'; |
||||||
|
import Volunteer from '../models/Volunteer.js'; |
||||||
|
import { requireAdmin } from '../middleware/auth.js'; |
||||||
|
|
||||||
|
const router = express.Router(); |
||||||
|
|
||||||
|
// Register a new volunteer (public route)
|
||||||
|
router.post('/register', async (req, res) => { |
||||||
|
try { |
||||||
|
const volunteer = new Volunteer(req.body); |
||||||
|
await volunteer.save(); |
||||||
|
res.status(201).json({ message: 'Volunteer registered successfully' }); |
||||||
|
} catch (error) { |
||||||
|
console.error('Volunteer registration error:', error); |
||||||
|
res.status(500).json({ message: 'Failed to register volunteer' }); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
// Admin routes - protected by requireAdmin middleware
|
||||||
|
router.get('/list', requireAdmin, async (req, res) => { |
||||||
|
try { |
||||||
|
const volunteers = await Volunteer.find().sort({ createdAt: -1 }); |
||||||
|
res.json(volunteers); |
||||||
|
} catch (error) { |
||||||
|
console.error('Error fetching volunteers:', error); |
||||||
|
res.status(500).json({ message: 'Failed to fetch volunteers' }); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
router.delete('/:id', requireAdmin, async (req, res) => { |
||||||
|
try { |
||||||
|
await Volunteer.findByIdAndDelete(req.params.id); |
||||||
|
res.json({ message: 'Volunteer deleted successfully' }); |
||||||
|
} catch (error) { |
||||||
|
console.error('Error deleting volunteer:', error); |
||||||
|
res.status(500).json({ message: 'Failed to delete volunteer' }); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
export default router; |
@ -0,0 +1,99 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; |
||||||
|
import { AuthProvider } from './context/AuthContext'; |
||||||
|
import Navbar from './components/Navbar'; |
||||||
|
import Home from './components/Home'; |
||||||
|
import About from './components/About'; |
||||||
|
import Projects from './components/Projects'; |
||||||
|
import Contact from './components/Contact'; |
||||||
|
import DonateFood from './components/DonateFood'; |
||||||
|
import Volunteer from './components/Volunteer'; |
||||||
|
import Login from './components/Login'; |
||||||
|
import Signup from './components/Signup'; |
||||||
|
import VolunteerAcknowledgment from './components/VolunteerAcknowledgment'; |
||||||
|
import News from './components/News'; |
||||||
|
import Footer from './components/Footer'; |
||||||
|
import 'bootstrap/dist/css/bootstrap.min.css'; |
||||||
|
import './styles/App.css'; |
||||||
|
|
||||||
|
function App() { |
||||||
|
return ( |
||||||
|
<AuthProvider> |
||||||
|
<Router> |
||||||
|
<Routes> |
||||||
|
{/* Public Routes */} |
||||||
|
<Route path="/" element={ |
||||||
|
<> |
||||||
|
<Navbar /> |
||||||
|
<Home /> |
||||||
|
<News /> |
||||||
|
<Footer /> |
||||||
|
</> |
||||||
|
} /> |
||||||
|
<Route path="/about" element={ |
||||||
|
<> |
||||||
|
<Navbar /> |
||||||
|
<About /> |
||||||
|
<Footer /> |
||||||
|
</> |
||||||
|
} /> |
||||||
|
<Route path="/projects" element={ |
||||||
|
<> |
||||||
|
<Navbar /> |
||||||
|
<Projects /> |
||||||
|
<Footer /> |
||||||
|
</> |
||||||
|
} /> |
||||||
|
<Route path="/contact" element={ |
||||||
|
<> |
||||||
|
<Navbar /> |
||||||
|
<Contact /> |
||||||
|
<Footer /> |
||||||
|
</> |
||||||
|
} /> |
||||||
|
<Route path="/donate-food" element={ |
||||||
|
<> |
||||||
|
<Navbar /> |
||||||
|
<DonateFood /> |
||||||
|
<Footer /> |
||||||
|
</> |
||||||
|
} /> |
||||||
|
<Route path="/volunteer" element={ |
||||||
|
<> |
||||||
|
<Navbar /> |
||||||
|
<Volunteer /> |
||||||
|
<Footer /> |
||||||
|
</> |
||||||
|
} /> |
||||||
|
<Route path="/volunteer/acknowledgment" element={ |
||||||
|
<> |
||||||
|
<Navbar /> |
||||||
|
<VolunteerAcknowledgment /> |
||||||
|
<Footer /> |
||||||
|
</> |
||||||
|
} /> |
||||||
|
<Route path="/signup" element={ |
||||||
|
<> |
||||||
|
<Navbar /> |
||||||
|
<div className="page-wrapper"> |
||||||
|
<Signup /> |
||||||
|
</div> |
||||||
|
<Footer /> |
||||||
|
</> |
||||||
|
} /> |
||||||
|
<Route path="/login" element={ |
||||||
|
<> |
||||||
|
<Navbar /> |
||||||
|
<div className="page-wrapper"> |
||||||
|
<Login /> |
||||||
|
</div> |
||||||
|
<Footer /> |
||||||
|
</> |
||||||
|
} /> |
||||||
|
</Routes> |
||||||
|
</Router> |
||||||
|
</AuthProvider> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export default App; |
@ -0,0 +1,63 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { Users, Leaf, Globe } from 'lucide-react'; |
||||||
|
|
||||||
|
const About = () => { |
||||||
|
return ( |
||||||
|
<div className="about py-5"> |
||||||
|
<div className="container"> |
||||||
|
<div className="text-center mb-5"> |
||||||
|
<h1 className="display-4 mb-3">About EcoConnect</h1> |
||||||
|
<p className="lead">An innovative platform tackling food waste by connecting surplus food donors with those in need.</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="row mb-5"> |
||||||
|
<div className="col-md-6"> |
||||||
|
<img |
||||||
|
src="https://images.unsplash.com/photo-1488459716781-31db52582fe9" |
||||||
|
alt="Team working together" |
||||||
|
className="img-fluid rounded shadow-lg" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div className="col-md-6 d-flex flex-column justify-content-center"> |
||||||
|
<h2 className="mb-4">Our Mission</h2> |
||||||
|
<p className="mb-4">EcoConnect is an innovative platform designed to tackle the growing issue of food waste by connecting surplus food donors with organizations that serve those in need. We allow donors, including supermarkets, restaurants, food manufacturers, and party venues, to log and donate surplus food.</p> |
||||||
|
<p className="mb-4">The platform ensures food safety through a detailed checklist followed by volunteers during pick-up and offers partnerships with food experts and local authorities for quality assurance.</p> |
||||||
|
<p>EcoConnect also manages the logistics of food transportation, with a focus on both direct delivery and collaboration with local food banks, shelters, and orphanages. With liability waivers for both donors and recipients, the platform fosters a safe and reliable way to redistribute food, reducing waste while supporting vulnerable communities.</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="row g-4"> |
||||||
|
<div className="col-md-4"> |
||||||
|
<div className="card h-100 border-0 shadow-sm"> |
||||||
|
<div className="card-body text-center"> |
||||||
|
<Users className="text-success mb-3" size={40} /> |
||||||
|
<h3 className="h5 mb-3">Community First</h3> |
||||||
|
<p>Connecting food donors with recipients and volunteers to create a sustainable food ecosystem.</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="col-md-4"> |
||||||
|
<div className="card h-100 border-0 shadow-sm"> |
||||||
|
<div className="card-body text-center"> |
||||||
|
<Leaf className="text-success mb-3" size={40} /> |
||||||
|
<h3 className="h5 mb-3">Sustainable Impact</h3> |
||||||
|
<p>Reducing food waste while supporting communities in need through efficient food redistribution.</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="col-md-4"> |
||||||
|
<div className="card h-100 border-0 shadow-sm"> |
||||||
|
<div className="card-body text-center"> |
||||||
|
<Globe className="text-success mb-3" size={40} /> |
||||||
|
<h3 className="h5 mb-3">Global Reach</h3> |
||||||
|
<p>Building a network of donors, volunteers, and recipients to maximize our impact on food waste reduction.</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default About; |
@ -0,0 +1,161 @@ |
|||||||
|
import React, { useState } from 'react'; |
||||||
|
import { Mail, Phone, MapPin } from 'lucide-react'; |
||||||
|
import { useNavigate } from 'react-router-dom'; |
||||||
|
|
||||||
|
const Contact = () => { |
||||||
|
const navigate = useNavigate(); |
||||||
|
const [showNotification, setShowNotification] = useState(false); |
||||||
|
const [loading, setLoading] = useState(false); |
||||||
|
const [formData, setFormData] = useState({ |
||||||
|
name: '', |
||||||
|
email: '', |
||||||
|
subject: '', |
||||||
|
message: '' |
||||||
|
}); |
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => { |
||||||
|
setFormData({ |
||||||
|
...formData, |
||||||
|
[e.target.id]: e.target.value |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const showSuccessAndRedirect = () => { |
||||||
|
setShowNotification(true); |
||||||
|
setTimeout(() => { |
||||||
|
navigate('/'); |
||||||
|
}, 2000); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => { |
||||||
|
e.preventDefault(); |
||||||
|
setLoading(true); |
||||||
|
|
||||||
|
try { |
||||||
|
// Simulate API call
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000)); |
||||||
|
showSuccessAndRedirect(); |
||||||
|
} catch (error) { |
||||||
|
console.error('Contact form error:', error); |
||||||
|
} finally { |
||||||
|
setLoading(false); |
||||||
|
} |
||||||
|
}; |
||||||
|
return ( |
||||||
|
<div className="contact py-5"> |
||||||
|
<div className="container"> |
||||||
|
{showNotification && ( |
||||||
|
<div className="position-fixed top-0 start-50 translate-middle-x p-3" style={{ zIndex: 1050 }}> |
||||||
|
<div className="toast show bg-success text-white" role="alert" style={{ |
||||||
|
minWidth: '300px', |
||||||
|
borderRadius: '10px', |
||||||
|
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)' |
||||||
|
}}> |
||||||
|
<div className="toast-body text-center py-3"> |
||||||
|
<strong>✨ Message sent successfully! We'll contact you soon.</strong> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
)} |
||||||
|
<div className="text-center mb-5"> |
||||||
|
<h1 className="display-4 mb-3">Contact Us</h1> |
||||||
|
<p className="lead">Get in touch with our team to learn more about how you can get involved.</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="row"> |
||||||
|
<div className="col-md-6 mb-4 mb-md-0"> |
||||||
|
<div className="card border-0 shadow-sm h-100"> |
||||||
|
<div className="card-body"> |
||||||
|
<h2 className="h4 mb-4">Send us a message</h2> |
||||||
|
<form onSubmit={handleSubmit}> |
||||||
|
<div className="mb-3"> |
||||||
|
<label htmlFor="name" className="form-label">Name</label> |
||||||
|
<input |
||||||
|
type="text" |
||||||
|
className="form-control" |
||||||
|
id="name" |
||||||
|
value={formData.name} |
||||||
|
onChange={handleChange} |
||||||
|
required |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div className="mb-3"> |
||||||
|
<label htmlFor="email" className="form-label">Email</label> |
||||||
|
<input |
||||||
|
type="email" |
||||||
|
className="form-control" |
||||||
|
id="email" |
||||||
|
value={formData.email} |
||||||
|
onChange={handleChange} |
||||||
|
required |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div className="mb-3"> |
||||||
|
<label htmlFor="subject" className="form-label">Subject</label> |
||||||
|
<input |
||||||
|
type="text" |
||||||
|
className="form-control" |
||||||
|
id="subject" |
||||||
|
value={formData.subject} |
||||||
|
onChange={handleChange} |
||||||
|
required |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div className="mb-3"> |
||||||
|
<label htmlFor="message" className="form-label">Message</label> |
||||||
|
<textarea |
||||||
|
className="form-control" |
||||||
|
id="message" |
||||||
|
rows={5} |
||||||
|
value={formData.message} |
||||||
|
onChange={handleChange} |
||||||
|
required |
||||||
|
></textarea> |
||||||
|
</div> |
||||||
|
<button |
||||||
|
type="submit" |
||||||
|
className="btn btn-success" |
||||||
|
disabled={loading} |
||||||
|
> |
||||||
|
{loading ? 'Sending...' : 'Send Message'} |
||||||
|
</button> |
||||||
|
</form> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="col-md-6"> |
||||||
|
<div className="card border-0 shadow-sm h-100"> |
||||||
|
<div className="card-body"> |
||||||
|
<h2 className="h4 mb-4">Contact Information</h2> |
||||||
|
<div className="d-flex mb-3"> |
||||||
|
<Mail className="text-success me-3" /> |
||||||
|
<div> |
||||||
|
<h3 className="h6 mb-1">Email</h3> |
||||||
|
<p className="mb-0">info@ecoconnect.com</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="d-flex mb-3"> |
||||||
|
<Phone className="text-success me-3" /> |
||||||
|
<div> |
||||||
|
<h3 className="h6 mb-1">Phone</h3> |
||||||
|
<p className="mb-0">+1 (555) 123-4567</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="d-flex"> |
||||||
|
<MapPin className="text-success me-3" /> |
||||||
|
<div> |
||||||
|
<h3 className="h6 mb-1">Address</h3> |
||||||
|
<p className="mb-0">123 Green Street<br />Eco City, EC 12345</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default Contact; |
@ -0,0 +1,148 @@ |
|||||||
|
import React, { useState } from 'react'; |
||||||
|
import { Clock, Utensils, MapPin, Calendar } from 'lucide-react'; |
||||||
|
|
||||||
|
const DonateFood = () => { |
||||||
|
const [formData, setFormData] = useState({ |
||||||
|
foodType: '', |
||||||
|
quantity: '', |
||||||
|
expiryDate: '', |
||||||
|
location: '', |
||||||
|
description: '', |
||||||
|
storageType: 'room-temperature' |
||||||
|
}); |
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => { |
||||||
|
e.preventDefault(); |
||||||
|
// Handle form submission here
|
||||||
|
console.log(formData); |
||||||
|
alert('Thank you for your donation! We will contact you soon.'); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => { |
||||||
|
setFormData({ |
||||||
|
...formData, |
||||||
|
[e.target.name]: e.target.value |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="container py-5"> |
||||||
|
<div className="row justify-content-center"> |
||||||
|
<div className="col-md-8"> |
||||||
|
<div className="card border-0 shadow-sm"> |
||||||
|
<div className="card-body p-5"> |
||||||
|
<h1 className="text-center mb-4">Donate Food</h1> |
||||||
|
<p className="text-center text-muted mb-5">Help us reduce food waste and feed those in need</p> |
||||||
|
|
||||||
|
<form onSubmit={handleSubmit}> |
||||||
|
<div className="mb-4"> |
||||||
|
<label className="form-label d-flex align-items-center"> |
||||||
|
<Utensils className="me-2 text-success" size={20} /> |
||||||
|
Food Type |
||||||
|
</label> |
||||||
|
<select
|
||||||
|
className="form-select" |
||||||
|
name="foodType" |
||||||
|
value={formData.foodType} |
||||||
|
onChange={handleChange} |
||||||
|
required |
||||||
|
> |
||||||
|
<option value="">Select food type</option> |
||||||
|
<option value="cooked">Cooked Food</option> |
||||||
|
<option value="packaged">Packaged Food</option> |
||||||
|
<option value="fresh">Fresh Produce</option> |
||||||
|
<option value="grains">Grains & Cereals</option> |
||||||
|
<option value="dairy">Dairy Products</option> |
||||||
|
<option value="other">Other</option> |
||||||
|
</select> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="mb-4"> |
||||||
|
<label className="form-label d-flex align-items-center"> |
||||||
|
<Clock className="me-2 text-success" size={20} /> |
||||||
|
Storage Type |
||||||
|
</label> |
||||||
|
<select |
||||||
|
className="form-select" |
||||||
|
name="storageType" |
||||||
|
value={formData.storageType} |
||||||
|
onChange={handleChange} |
||||||
|
required |
||||||
|
> |
||||||
|
<option value="room-temperature">Room Temperature</option> |
||||||
|
<option value="refrigerated">Refrigerated</option> |
||||||
|
<option value="frozen">Frozen</option> |
||||||
|
</select> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="mb-4"> |
||||||
|
<label className="form-label d-flex align-items-center"> |
||||||
|
<Calendar className="me-2 text-success" size={20} /> |
||||||
|
Expiry Date |
||||||
|
</label> |
||||||
|
<input |
||||||
|
type="date" |
||||||
|
className="form-control" |
||||||
|
name="expiryDate" |
||||||
|
value={formData.expiryDate} |
||||||
|
onChange={handleChange} |
||||||
|
required |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="mb-4"> |
||||||
|
<label className="form-label d-flex align-items-center"> |
||||||
|
<MapPin className="me-2 text-success" size={20} /> |
||||||
|
Pickup Location |
||||||
|
</label> |
||||||
|
<input |
||||||
|
type="text" |
||||||
|
className="form-control" |
||||||
|
name="location" |
||||||
|
value={formData.location} |
||||||
|
onChange={handleChange} |
||||||
|
placeholder="Enter pickup address" |
||||||
|
required |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="mb-4"> |
||||||
|
<label className="form-label">Quantity</label> |
||||||
|
<input |
||||||
|
type="text" |
||||||
|
className="form-control" |
||||||
|
name="quantity" |
||||||
|
value={formData.quantity} |
||||||
|
onChange={handleChange} |
||||||
|
placeholder="e.g., 5 kg, 3 boxes, 10 portions" |
||||||
|
required |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="mb-4"> |
||||||
|
<label className="form-label">Additional Details</label> |
||||||
|
<textarea |
||||||
|
className="form-control" |
||||||
|
name="description" |
||||||
|
value={formData.description} |
||||||
|
onChange={handleChange} |
||||||
|
rows={3} |
||||||
|
placeholder="Any special instructions or details about the food" |
||||||
|
></textarea> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="text-center"> |
||||||
|
<button type="submit" className="btn btn-success btn-lg px-5"> |
||||||
|
Submit Donation |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</form> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default DonateFood; |
@ -0,0 +1,27 @@ |
|||||||
|
import React from 'react'; |
||||||
|
|
||||||
|
const DonateModal = () => { |
||||||
|
return ( |
||||||
|
<div className="modal fade" id="donateModal" tabIndex={-1} aria-labelledby="donateModalLabel" aria-hidden="true"> |
||||||
|
<div className="modal-dialog modal-dialog-centered"> |
||||||
|
<div className="modal-content"> |
||||||
|
<div className="modal-header"> |
||||||
|
<h5 className="modal-title" id="donateModalLabel">Donate to EcoConnect</h5> |
||||||
|
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> |
||||||
|
</div> |
||||||
|
<div className="modal-body text-center"> |
||||||
|
<p className="mb-4">Scan this QR code to make a donation</p> |
||||||
|
<img |
||||||
|
src="public\WhatsApp Image 2025-01-13 at 09.14.18_6f19832d.jpg" |
||||||
|
alt="Donation QR Code" |
||||||
|
className="img-fluid" |
||||||
|
style={{ maxWidth: '300px' }} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default DonateModal; |
@ -0,0 +1,43 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { Facebook, Twitter, Instagram, Mail } from 'lucide-react'; |
||||||
|
import Logo from './Logo'; |
||||||
|
|
||||||
|
const Footer = () => { |
||||||
|
return ( |
||||||
|
<footer className="bg-dark text-light py-4"> |
||||||
|
<div className="container"> |
||||||
|
<div className="row"> |
||||||
|
<div className="col-md-4"> |
||||||
|
<div className="footer-logo-wrapper mb-3"> |
||||||
|
<Logo height={40} className="footer-logo" isFooter={true} /> |
||||||
|
</div> |
||||||
|
<p>Building a sustainable future together.</p> |
||||||
|
</div> |
||||||
|
<div className="col-md-4"> |
||||||
|
<h5>Quick Links</h5> |
||||||
|
<ul className="list-unstyled"> |
||||||
|
<li><a href="/about" className="text-light">About Us</a></li> |
||||||
|
<li><a href="/projects" className="text-light">Projects</a></li> |
||||||
|
<li><a href="/contact" className="text-light">Contact</a></li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
<div className="col-md-4"> |
||||||
|
<h5>Connect With Us</h5> |
||||||
|
<div className="social-icons"> |
||||||
|
<a href="#" className="text-light me-3"><Facebook /></a> |
||||||
|
<a href="#" className="text-light me-3"><Twitter /></a> |
||||||
|
<a href="#" className="text-light me-3"><Instagram /></a> |
||||||
|
<a href="#" className="text-light"><Mail /></a> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<hr className="mt-4" /> |
||||||
|
<div className="text-center"> |
||||||
|
<p className="mb-0">© 2024 EcoConnect. All rights reserved.</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</footer> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default Footer; |
@ -0,0 +1,545 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { LogIn, Leaf, Heart, Globe, Utensils, ArrowRight, Users, Trophy, MapPin, Store } from 'lucide-react'; |
||||||
|
import { useNavigate } from 'react-router-dom'; |
||||||
|
|
||||||
|
const Home = () => { |
||||||
|
const navigate = useNavigate(); |
||||||
|
|
||||||
|
const handleGoogleLogin = () => { |
||||||
|
window.location.href = 'http://localhost:5000/auth/google'; |
||||||
|
}; |
||||||
|
|
||||||
|
const handleDonateFood = () => { |
||||||
|
navigate('/donate-food'); |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="home"> |
||||||
|
<section className="hero"> |
||||||
|
<div className="container"> |
||||||
|
<div className="row align-items-center"> |
||||||
|
<div className="col-lg-6 hero-content"> |
||||||
|
<h1 className="mb-4">Make Earth<br />A Better Place</h1> |
||||||
|
<p className="lead text-white mb-5"> |
||||||
|
Join our innovative platform connecting surplus food donors with those in need. Together,
|
||||||
|
we can reduce food waste and support our communities. |
||||||
|
</p> |
||||||
|
<div className="d-flex gap-3"> |
||||||
|
<button
|
||||||
|
className="btn btn-success btn-lg" |
||||||
|
onClick={handleDonateFood} |
||||||
|
> |
||||||
|
<Utensils className="me-2" size={20} /> |
||||||
|
Donate Food |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="col-lg-6 mt-5 mt-lg-0"> |
||||||
|
<div className="position-relative"> |
||||||
|
<img |
||||||
|
src="https://images.unsplash.com/photo-1590779033100-9f60a05a013d" |
||||||
|
alt="Fresh Produce and Vegetables" |
||||||
|
className="img-fluid rounded-3 shadow-lg" |
||||||
|
style={{ transform: 'rotate(3deg)' }} |
||||||
|
/> |
||||||
|
<div className="position-absolute top-0 start-0 translate-middle bg-success text-white p-3 rounded-circle"> |
||||||
|
<Trophy size={24} /> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</section> |
||||||
|
|
||||||
|
<section className="features"> |
||||||
|
<div className="container"> |
||||||
|
<div className="text-center mb-5"> |
||||||
|
<div className="why-choose-wrapper"> |
||||||
|
<h2 className="eco-title text-success">WHY CHOOSE?</h2> |
||||||
|
<div className="eco-connect-wrapper mt-4"> |
||||||
|
<h3 className="display-2 fw-bold coral-text">EcoConnect</h3> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<p className="lead text-muted mx-auto" style={{ maxWidth: '700px' }}> |
||||||
|
An innovative platform designed to tackle food waste by connecting donors with organizations serving those in need,
|
||||||
|
ensuring safe and efficient food redistribution while supporting vulnerable communities. |
||||||
|
</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="feature-image-wrapper mb-5"> |
||||||
|
<img |
||||||
|
src="https://images.unsplash.com/photo-1542838132-92c53300491e" |
||||||
|
alt="Sustainable Food" |
||||||
|
className="feature-image rounded-3 shadow-lg" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="row g-4 justify-content-center"> |
||||||
|
<div className="col-md-3"> |
||||||
|
<div className="feature-card h-100"> |
||||||
|
<div className="card-body text-center p-4"> |
||||||
|
<div className="icon-wrapper mb-4"> |
||||||
|
<Leaf size={48} className="text-success" /> |
||||||
|
</div> |
||||||
|
<h3 className="h4 mb-3">Help Sustain the Environment</h3> |
||||||
|
<p className="text-muted mb-3">Join our eco-friendly initiatives and make a lasting impact on our planet's future</p> |
||||||
|
<div className="progress mt-3" style={{ height: '4px' }}> |
||||||
|
<div
|
||||||
|
className="progress-bar bg-success"
|
||||||
|
style={{ width: '75%' }} |
||||||
|
role="progressbar" |
||||||
|
aria-valuenow={75} |
||||||
|
aria-valuemin={0} |
||||||
|
aria-valuemax={100} |
||||||
|
></div> |
||||||
|
</div> |
||||||
|
<div className="mt-3"> |
||||||
|
<span className="badge bg-success-subtle text-success px-3 py-2">75% Goal Reached</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="col-md-3"> |
||||||
|
<div className="feature-card h-100"> |
||||||
|
<div className="card-body text-center p-4"> |
||||||
|
<div className="icon-wrapper mb-4"> |
||||||
|
<Globe size={48} className="text-success" /> |
||||||
|
</div> |
||||||
|
<h3 className="h4 mb-3">Reduce Carbon Footprint</h3> |
||||||
|
<p className="text-muted mb-3">Combat climate change through sustainable food practices and waste reduction</p> |
||||||
|
<div className="progress mt-3" style={{ height: '4px' }}> |
||||||
|
<div
|
||||||
|
className="progress-bar bg-success"
|
||||||
|
style={{ width: '85%' }} |
||||||
|
role="progressbar" |
||||||
|
aria-valuenow={85} |
||||||
|
aria-valuemin={0} |
||||||
|
aria-valuemax={100} |
||||||
|
></div> |
||||||
|
</div> |
||||||
|
<div className="mt-3"> |
||||||
|
<span className="badge bg-success-subtle text-success px-3 py-2">85% Goal Reached</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="col-md-3"> |
||||||
|
<div className="feature-card h-100"> |
||||||
|
<div className="card-body text-center p-4"> |
||||||
|
<div className="icon-wrapper mb-4"> |
||||||
|
<MapPin size={48} className="text-success" /> |
||||||
|
</div> |
||||||
|
<h3 className="h4 mb-3">Rescue Food Near You</h3> |
||||||
|
<p className="text-muted mb-3">Connect with local initiatives to save and redistribute surplus food</p> |
||||||
|
<div className="progress mt-3" style={{ height: '4px' }}> |
||||||
|
<div
|
||||||
|
className="progress-bar bg-success"
|
||||||
|
style={{ width: '90%' }} |
||||||
|
role="progressbar" |
||||||
|
aria-valuenow={90} |
||||||
|
aria-valuemin={0} |
||||||
|
aria-valuemax={100} |
||||||
|
></div> |
||||||
|
</div> |
||||||
|
<div className="mt-3"> |
||||||
|
<span className="badge bg-success-subtle text-success px-3 py-2">90% Goal Reached</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="col-md-3"> |
||||||
|
<div className="feature-card h-100"> |
||||||
|
<div className="card-body text-center p-4"> |
||||||
|
<div className="icon-wrapper mb-4"> |
||||||
|
<Store size={48} className="text-success" /> |
||||||
|
</div> |
||||||
|
<h3 className="h4 mb-3">Support Local Business</h3> |
||||||
|
<p className="text-muted mb-3">Partner with local businesses to create sustainable food ecosystems</p> |
||||||
|
<div className="progress mt-3" style={{ height: '4px' }}> |
||||||
|
<div
|
||||||
|
className="progress-bar bg-success"
|
||||||
|
style={{ width: '80%' }} |
||||||
|
role="progressbar" |
||||||
|
aria-valuenow={80} |
||||||
|
aria-valuemin={0} |
||||||
|
aria-valuemax={100} |
||||||
|
></div> |
||||||
|
</div> |
||||||
|
<div className="mt-3"> |
||||||
|
<span className="badge bg-success-subtle text-success px-3 py-2">80% Goal Reached</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</section> |
||||||
|
|
||||||
|
<section className="food-waste-challenges py-5 bg-light"> |
||||||
|
<div className="container"> |
||||||
|
<div className="text-center mb-5"> |
||||||
|
<span className="badge bg-success-subtle text-success mb-3 px-3 py-2 rounded-pill">Global Challenge</span> |
||||||
|
<h2 className="display-4 fw-bold mb-3">Food Waste: A Global Crisis</h2> |
||||||
|
<p className="lead text-muted mx-auto" style={{ maxWidth: '800px' }}> |
||||||
|
Food waste is one of the most pressing environmental and social challenges of our time.
|
||||||
|
Together, we can make a difference through awareness and action. |
||||||
|
</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="row g-4 mb-5"> |
||||||
|
<div className="col-md-6"> |
||||||
|
<div className="card h-100 border-0 shadow-sm"> |
||||||
|
<div className="card-body p-4"> |
||||||
|
<h3 className="h4 mb-4 text-success">The Challenge</h3> |
||||||
|
<ul className="list-unstyled mb-0"> |
||||||
|
<li className="mb-3 d-flex align-items-start"> |
||||||
|
<div className="bg-success-subtle rounded-circle p-2 me-3"> |
||||||
|
<Trophy size={20} className="text-success" /> |
||||||
|
</div> |
||||||
|
<div> |
||||||
|
<h4 className="h6 mb-1">Production vs Waste</h4> |
||||||
|
<p className="text-muted mb-0">While we produce enough food to feed everyone, one-third is wasted globally.</p> |
||||||
|
</div> |
||||||
|
</li> |
||||||
|
<li className="mb-3 d-flex align-items-start"> |
||||||
|
<div className="bg-success-subtle rounded-circle p-2 me-3"> |
||||||
|
<Globe size={20} className="text-success" /> |
||||||
|
</div> |
||||||
|
<div> |
||||||
|
<h4 className="h6 mb-1">Environmental Impact</h4> |
||||||
|
<p className="text-muted mb-0">Food waste contributes to 8-10% of global greenhouse gas emissions.</p> |
||||||
|
</div> |
||||||
|
</li> |
||||||
|
<li className="d-flex align-items-start"> |
||||||
|
<div className="bg-success-subtle rounded-circle p-2 me-3"> |
||||||
|
<Heart size={20} className="text-success" /> |
||||||
|
</div> |
||||||
|
<div> |
||||||
|
<h4 className="h6 mb-1">Social Impact</h4> |
||||||
|
<p className="text-muted mb-0">Over 800 million people face hunger while perfectly good food goes to waste.</p> |
||||||
|
</div> |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="col-md-6"> |
||||||
|
<div className="card h-100 border-0 shadow-sm"> |
||||||
|
<div className="card-body p-4"> |
||||||
|
<h3 className="h4 mb-4 text-success">Our Solution</h3> |
||||||
|
<ul className="list-unstyled mb-0"> |
||||||
|
<li className="mb-3 d-flex align-items-start"> |
||||||
|
<div className="bg-success-subtle rounded-circle p-2 me-3"> |
||||||
|
<Users size={20} className="text-success" /> |
||||||
|
</div> |
||||||
|
<div> |
||||||
|
<h4 className="h6 mb-1">Community Connection</h4> |
||||||
|
<p className="text-muted mb-0">We connect food donors with local organizations and volunteers.</p> |
||||||
|
</div> |
||||||
|
</li> |
||||||
|
<li className="mb-3 d-flex align-items-start"> |
||||||
|
<div className="bg-success-subtle rounded-circle p-2 me-3"> |
||||||
|
<Utensils size={20} className="text-success" /> |
||||||
|
</div> |
||||||
|
<div> |
||||||
|
<h4 className="h6 mb-1">Food Recovery</h4> |
||||||
|
<p className="text-muted mb-0">Efficient collection and distribution of surplus food to those in need.</p> |
||||||
|
</div> |
||||||
|
</li> |
||||||
|
<li className="d-flex align-items-start"> |
||||||
|
<div className="bg-success-subtle rounded-circle p-2 me-3"> |
||||||
|
<Store size={20} className="text-success" /> |
||||||
|
</div> |
||||||
|
<div> |
||||||
|
<h4 className="h6 mb-1">Business Integration</h4> |
||||||
|
<p className="text-muted mb-0">Helping businesses reduce waste while supporting their communities.</p> |
||||||
|
</div> |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="text-center"> |
||||||
|
<img |
||||||
|
src="https://images.unsplash.com/photo-1488459716781-31db52582fe9" |
||||||
|
alt="Community Food Distribution" |
||||||
|
className="img-fluid rounded-3 shadow-lg" |
||||||
|
style={{ maxHeight: '400px', width: '100%', objectFit: 'cover' }} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</section> |
||||||
|
|
||||||
|
<section className="education-section py-5"> |
||||||
|
<div className="container"> |
||||||
|
<div className="text-center mb-5"> |
||||||
|
<img |
||||||
|
src="https://images.unsplash.com/photo-1605493725784-75cf3c82b158" |
||||||
|
alt="Food Waste Impact" |
||||||
|
className="img-fluid rounded-3 shadow-lg mb-5" |
||||||
|
style={{ maxHeight: '400px', width: '100%', objectFit: 'cover' }} |
||||||
|
/> |
||||||
|
<span className="badge bg-success-subtle text-success mb-3 px-3 py-2 rounded-pill">Food Waste Education</span> |
||||||
|
<h2 className="display-4 fw-bold mb-3">Understanding Food Waste</h2> |
||||||
|
<p className="lead text-muted mx-auto" style={{ maxWidth: '700px' }}> |
||||||
|
Learn about the impact of food waste on our environment and how you can make a difference |
||||||
|
</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="row g-4 mb-5 justify-content-center"> |
||||||
|
<div className="col-md-3"> |
||||||
|
<div className="stat-card text-center h-100 p-4 border-0 shadow-sm rounded-3"> |
||||||
|
<div className="display-3 text-success fw-bold mb-2">33%</div> |
||||||
|
<p className="text-muted mb-0">Global Food Production Wasted Annually</p> |
||||||
|
<div className="progress mt-3" style={{ height: '4px' }}> |
||||||
|
<div className="progress-bar bg-success" style={{ width: '33%' }}></div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="col-md-3"> |
||||||
|
<div className="stat-card text-center h-100 p-4 border-0 shadow-sm rounded-3"> |
||||||
|
<div className="display-3 text-success fw-bold mb-2">8%</div> |
||||||
|
<p className="text-muted mb-0">Global Greenhouse Gas Emissions from Food Waste</p> |
||||||
|
<div className="progress mt-3" style={{ height: '4px' }}> |
||||||
|
<div className="progress-bar bg-success" style={{ width: '8%' }}></div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="col-md-3"> |
||||||
|
<div className="stat-card text-center h-100 p-4 border-0 shadow-sm rounded-3"> |
||||||
|
<div className="display-3 text-success fw-bold mb-2">1.3B</div> |
||||||
|
<p className="text-muted mb-0">Tonnes of Food Wasted Worldwide</p> |
||||||
|
<div className="progress mt-3" style={{ height: '4px' }}> |
||||||
|
<div className="progress-bar bg-success" style={{ width: '75%' }}></div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="col-md-3"> |
||||||
|
<div className="stat-card text-center h-100 p-4 border-0 shadow-sm rounded-3"> |
||||||
|
<div className="display-3 text-success fw-bold mb-2">$1T</div> |
||||||
|
<p className="text-muted mb-0">Annual Economic Cost of Food Waste</p> |
||||||
|
<div className="progress mt-3" style={{ height: '4px' }}> |
||||||
|
<div className="progress-bar bg-success" style={{ width: '60%' }}></div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="did-you-know-carousel mb-4"> |
||||||
|
<div className="fact-carousel bg-success-subtle p-4 rounded-3 position-relative"> |
||||||
|
<h3 className="h4 text-success mb-3">Did You Know?</h3> |
||||||
|
<div className="fact-slides"> |
||||||
|
{[ |
||||||
|
"About one-third of all food produced for human consumption goes to waste globally.", |
||||||
|
"Food waste generates 3.3 billion tons of greenhouse gases annually.", |
||||||
|
"The average American family wastes $1,500 worth of food per year.", |
||||||
|
"Reducing food waste is ranked as the #3 solution to fight climate change.", |
||||||
|
"Just one quarter of all wasted food could feed 870 million hungry people.", |
||||||
|
"Food waste in landfills produces methane, a greenhouse gas 25 times more potent than CO2." |
||||||
|
].map((fact, index) => ( |
||||||
|
<div key={index} className="fact-slide"> |
||||||
|
<p className="mb-0">{fact}</p> |
||||||
|
</div> |
||||||
|
))} |
||||||
|
</div> |
||||||
|
<div className="fact-controls"> |
||||||
|
<div className="fact-indicators"> |
||||||
|
{[...Array(6)].map((_, i) => ( |
||||||
|
<div key={i} className="fact-indicator"></div> |
||||||
|
))} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="mb-4"> |
||||||
|
<span className="badge bg-success-subtle text-success mb-2">Take Action</span> |
||||||
|
<h3 className="h2">Tips to Reduce Food Waste</h3> |
||||||
|
<p className="text-muted">Simple steps that make a big difference in reducing food waste</p> |
||||||
|
</div> |
||||||
|
<div className="tips-list"> |
||||||
|
<div className="tip-item mb-3 d-flex align-items-start"> |
||||||
|
<div className="tip-number bg-success-subtle text-success rounded-circle p-2 me-3">1</div> |
||||||
|
<div> |
||||||
|
<h4 className="h5 mb-2">Plan Your Meals</h4> |
||||||
|
<p className="text-muted">Create a weekly meal plan and shop with a list to avoid overbuying.</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="tip-item mb-3 d-flex align-items-start"> |
||||||
|
<div className="tip-number bg-success-subtle text-success rounded-circle p-2 me-3">2</div> |
||||||
|
<div> |
||||||
|
<h4 className="h5 mb-2">Store Food Properly</h4> |
||||||
|
<p className="text-muted">Learn proper storage techniques to extend food freshness.</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="tip-item mb-3 d-flex align-items-start"> |
||||||
|
<div className="tip-number bg-success-subtle text-success rounded-circle p-2 me-3">3</div> |
||||||
|
<div> |
||||||
|
<h4 className="h5 mb-2">Use Leftovers Creatively</h4> |
||||||
|
<p className="text-muted">Transform leftovers into new meals instead of throwing them away.</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="tip-item d-flex align-items-start"> |
||||||
|
<div className="tip-number bg-success-subtle text-success rounded-circle p-2 me-3">4</div> |
||||||
|
<div> |
||||||
|
<h4 className="h5 mb-2">Donate Excess Food</h4> |
||||||
|
<p className="text-muted">Connect with local food banks and donation centers through our platform.</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</section> |
||||||
|
|
||||||
|
<section className="community-section"> |
||||||
|
<div className="container"> |
||||||
|
<div className="row align-items-center"> |
||||||
|
<div className="col-lg-6 mb-5 mb-lg-0"> |
||||||
|
<img |
||||||
|
src="https://images.unsplash.com/photo-1542601906990-b4d3fb778b09" |
||||||
|
alt="Community" |
||||||
|
className="img-fluid rounded-3 shadow-lg" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div className="col-lg-6 ps-lg-5"> |
||||||
|
<h2 className="display-4 fw-bold mb-4">Join Our Growing Community</h2> |
||||||
|
<p className="lead mb-5"> |
||||||
|
Connect with like-minded individuals and organizations committed to making
|
||||||
|
our planet a better place for future generations. |
||||||
|
</p> |
||||||
|
<div className="row g-4"> |
||||||
|
<div className="col-md-4"> |
||||||
|
<div className="stats-card text-center"> |
||||||
|
<Users className="text-success mb-3" size={32} /> |
||||||
|
<div className="stats-number">5000+</div> |
||||||
|
<div className="stats-label">Active Members</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="col-md-4"> |
||||||
|
<div className="stats-card text-center"> |
||||||
|
<Leaf className="text-success mb-3" size={32} /> |
||||||
|
<div className="stats-number">200+</div> |
||||||
|
<div className="stats-label">Projects Done</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="col-md-4"> |
||||||
|
<div className="stats-card text-center"> |
||||||
|
<Globe className="text-success mb-3" size={32} /> |
||||||
|
<div className="stats-number">50+</div> |
||||||
|
<div className="stats-label">Partners</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</section> |
||||||
|
|
||||||
|
<section className="food-facts mt-5"> |
||||||
|
<div className="container"> |
||||||
|
<div className="text-center mb-4"> |
||||||
|
<span className="badge bg-success-subtle text-success mb-2">Food Facts</span> |
||||||
|
<h3 className="h2">Shocking Facts About Food Waste</h3> |
||||||
|
<p className="text-muted">Understanding the impact of food waste on our planet</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="row g-4"> |
||||||
|
<div className="col-md-6 col-lg-4"> |
||||||
|
<div className="fact-card"> |
||||||
|
<div className="fact-icon bg-success-subtle"> |
||||||
|
<Globe size={24} className="text-success" /> |
||||||
|
</div> |
||||||
|
<h4 className="fact-title">Global Impact</h4> |
||||||
|
<p className="fact-text">If food waste were a country, it would be the third-largest emitter of greenhouse gases after China and the USA.</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="col-md-6 col-lg-4"> |
||||||
|
<div className="fact-card"> |
||||||
|
<div className="fact-icon bg-success-subtle"> |
||||||
|
<Heart size={24} className="text-success" /> |
||||||
|
</div> |
||||||
|
<h4 className="fact-title">Hunger Reality</h4> |
||||||
|
<p className="fact-text">One in nine people globally are undernourished, yet we waste 1.3 billion tonnes of food annually.</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="col-md-6 col-lg-4"> |
||||||
|
<div className="fact-card"> |
||||||
|
<div className="fact-icon bg-success-subtle"> |
||||||
|
<Utensils size={24} className="text-success" /> |
||||||
|
</div> |
||||||
|
<h4 className="fact-title">Restaurant Waste</h4> |
||||||
|
<p className="fact-text">Restaurants generate about 11.4 million tonnes of food waste annually in just the United States alone.</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="col-md-6 col-lg-4"> |
||||||
|
<div className="fact-card"> |
||||||
|
<div className="fact-icon bg-success-subtle"> |
||||||
|
<Trophy size={24} className="text-success" /> |
||||||
|
</div> |
||||||
|
<h4 className="fact-title">Economic Cost</h4> |
||||||
|
<p className="fact-text">The global economic cost of food waste amounts to roughly $1 trillion per year.</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="col-md-6 col-lg-4"> |
||||||
|
<div className="fact-card"> |
||||||
|
<div className="fact-icon bg-success-subtle"> |
||||||
|
<Leaf size={24} className="text-success" /> |
||||||
|
</div> |
||||||
|
<h4 className="fact-title">Environmental Impact</h4> |
||||||
|
<p className="fact-text">Food waste accounts for about 8% of global greenhouse gas emissions.</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="col-md-6 col-lg-4"> |
||||||
|
<div className="fact-card"> |
||||||
|
<div className="fact-icon bg-success-subtle"> |
||||||
|
<Users size={24} className="text-success" /> |
||||||
|
</div> |
||||||
|
<h4 className="fact-title">Household Waste</h4> |
||||||
|
<p className="fact-text">The average family throws away about 30% of the food they purchase.</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="col-md-6 col-lg-4"> |
||||||
|
<div className="fact-card"> |
||||||
|
<div className="fact-icon bg-success-subtle"> |
||||||
|
<Globe size={24} className="text-success" /> |
||||||
|
</div> |
||||||
|
<h4 className="fact-title">Water Waste</h4> |
||||||
|
<p className="fact-text">Food waste represents about 25% of all freshwater consumption globally.</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="col-md-6 col-lg-4"> |
||||||
|
<div className="fact-card"> |
||||||
|
<div className="fact-icon bg-success-subtle"> |
||||||
|
<Store size={24} className="text-success" /> |
||||||
|
</div> |
||||||
|
<h4 className="fact-title">Retail Loss</h4> |
||||||
|
<p className="fact-text">Supermarkets throw away about 43 billion pounds of food every year.</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="col-md-6 col-lg-4"> |
||||||
|
<div className="fact-card"> |
||||||
|
<div className="fact-icon bg-success-subtle"> |
||||||
|
<MapPin size={24} className="text-success" /> |
||||||
|
</div> |
||||||
|
<h4 className="fact-title">Land Usage</h4> |
||||||
|
<p className="fact-text">Food waste occupies about 28% of the world's agricultural land.</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</section> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default Home; |
@ -0,0 +1,55 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html lang="en"> |
||||||
|
<head> |
||||||
|
<meta charset="UTF-8"> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||||
|
<title>Login - EcoConnect</title> |
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> |
||||||
|
<link href="auth.css" rel="stylesheet"> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div class="auth-container"> |
||||||
|
<div class="auth-box"> |
||||||
|
<div class="auth-header"> |
||||||
|
<h2>Welcome Back</h2> |
||||||
|
<p>Sign in to your account</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<form class="auth-form"> |
||||||
|
<div class="form-group"> |
||||||
|
<label for="email">Email Address</label> |
||||||
|
<input type="email" id="email" class="form-control" placeholder="Enter your email"> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="form-group"> |
||||||
|
<label for="password">Password</label> |
||||||
|
<input type="password" id="password" class="form-control" placeholder="Enter your password"> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="form-options"> |
||||||
|
<div class="remember-me"> |
||||||
|
<input type="checkbox" id="remember"> |
||||||
|
<label for="remember">Remember me</label> |
||||||
|
</div> |
||||||
|
<a href="#" class="forgot-password">Forgot Password?</a> |
||||||
|
</div> |
||||||
|
|
||||||
|
<button type="submit" class="auth-button">Sign In</button> |
||||||
|
|
||||||
|
<div class="auth-divider"> |
||||||
|
<span>OR</span> |
||||||
|
</div> |
||||||
|
|
||||||
|
<button type="button" class="social-button google"> |
||||||
|
<img src="https://upload.wikimedia.org/wikipedia/commons/5/53/Google_%22G%22_Logo.svg" alt="Google"> |
||||||
|
Continue with Google |
||||||
|
</button> |
||||||
|
|
||||||
|
<p class="auth-footer"> |
||||||
|
Don't have an account? <a href="signup.html">Sign up</a> |
||||||
|
</p> |
||||||
|
</form> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,119 @@ |
|||||||
|
import React, { useState } from 'react'; |
||||||
|
import { useNavigate } from 'react-router-dom'; |
||||||
|
import { Mail, Lock } from 'lucide-react'; |
||||||
|
import axios from 'axios'; |
||||||
|
import { useAuth } from '../context/AuthContext'; |
||||||
|
|
||||||
|
const Login = () => { |
||||||
|
const navigate = useNavigate(); |
||||||
|
const { checkUser } = useAuth(); |
||||||
|
const [formData, setFormData] = useState({ |
||||||
|
email: '', |
||||||
|
password: '' |
||||||
|
}); |
||||||
|
const [error, setError] = useState(''); |
||||||
|
const [loading, setLoading] = useState(false); |
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { |
||||||
|
setFormData({ |
||||||
|
...formData, |
||||||
|
[e.target.id]: e.target.value |
||||||
|
}); |
||||||
|
setError(''); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => { |
||||||
|
e.preventDefault(); |
||||||
|
setLoading(true); |
||||||
|
setError(''); |
||||||
|
|
||||||
|
try { |
||||||
|
// Simulate API call
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000)); |
||||||
|
|
||||||
|
// Set login status
|
||||||
|
sessionStorage.setItem('isLoggedIn', 'true'); |
||||||
|
|
||||||
|
// Navigate to home page
|
||||||
|
navigate('/'); |
||||||
|
} catch (error: any) { |
||||||
|
console.error('Login error:', error); |
||||||
|
setError('An error occurred. Please try again.'); |
||||||
|
} finally { |
||||||
|
setLoading(false); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="login-page"> |
||||||
|
<div className="container py-5"> |
||||||
|
<div className="row justify-content-center"> |
||||||
|
<div className="col-md-6 col-lg-4"> |
||||||
|
<div className="card border-0 shadow-sm login-card"> |
||||||
|
<div className="card-body p-4"> |
||||||
|
<h2 className="text-center login-title">Welcome Back</h2> |
||||||
|
<p className="text-center login-subtitle">Sign in to your account</p> |
||||||
|
|
||||||
|
{error && ( |
||||||
|
<div className="alert alert-danger" role="alert"> |
||||||
|
{error} |
||||||
|
</div> |
||||||
|
)} |
||||||
|
|
||||||
|
<form onSubmit={handleSubmit}> |
||||||
|
<div className="mb-3"> |
||||||
|
<label htmlFor="email" className="form-label d-flex align-items-center"> |
||||||
|
<Mail className="me-2 login-icon" size={18} /> |
||||||
|
Email |
||||||
|
</label> |
||||||
|
<input |
||||||
|
type="email" |
||||||
|
className="form-control" |
||||||
|
id="email" |
||||||
|
placeholder="Enter your email" |
||||||
|
value={formData.email} |
||||||
|
onChange={handleChange} |
||||||
|
required |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="mb-4"> |
||||||
|
<label htmlFor="password" className="form-label d-flex align-items-center"> |
||||||
|
<Lock className="me-2 login-icon" size={18} /> |
||||||
|
Password |
||||||
|
</label> |
||||||
|
<input |
||||||
|
type="password" |
||||||
|
className="form-control" |
||||||
|
id="password" |
||||||
|
placeholder="Enter your password" |
||||||
|
value={formData.password} |
||||||
|
onChange={handleChange} |
||||||
|
required |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-success w-100" |
||||||
|
disabled={loading} |
||||||
|
> |
||||||
|
{loading ? 'Signing in...' : 'Sign In'} |
||||||
|
</button> |
||||||
|
</form> |
||||||
|
|
||||||
|
<div className="mt-4 text-center"> |
||||||
|
<p className="mb-0"> |
||||||
|
Don't have an account? <a href="/signup" className="text-success">Sign up</a> |
||||||
|
</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default Login; |
@ -0,0 +1,159 @@ |
|||||||
|
import React from 'react'; |
||||||
|
|
||||||
|
interface LogoProps { |
||||||
|
className?: string; |
||||||
|
height?: number; |
||||||
|
isFooter?: boolean; |
||||||
|
} |
||||||
|
|
||||||
|
const Logo: React.FC<LogoProps> = ({ className = '', height = 32, isFooter = false }) => { |
||||||
|
return ( |
||||||
|
<svg |
||||||
|
className={`eco-logo ${className}`} |
||||||
|
height={height} |
||||||
|
viewBox="0 0 240 60" |
||||||
|
fill="none" |
||||||
|
xmlns="http://www.w3.org/2000/svg" |
||||||
|
> |
||||||
|
<defs> |
||||||
|
<linearGradient id="leafGradient" x1="0%" y1="0%" x2="100%" y2="100%"> |
||||||
|
<stop offset="0%" stopColor="#48bb78"> |
||||||
|
<animate |
||||||
|
attributeName="stop-color" |
||||||
|
values="#48bb78; #38a169; #48bb78" |
||||||
|
dur="4s" |
||||||
|
repeatCount="indefinite" |
||||||
|
/> |
||||||
|
</stop> |
||||||
|
<stop offset="100%" stopColor="#38a169"> |
||||||
|
<animate |
||||||
|
attributeName="stop-color" |
||||||
|
values="#38a169; #2f855a; #38a169" |
||||||
|
dur="4s" |
||||||
|
repeatCount="indefinite" |
||||||
|
/> |
||||||
|
</stop> |
||||||
|
</linearGradient> |
||||||
|
<filter id="glow"> |
||||||
|
<feGaussianBlur stdDeviation="1.5" result="coloredBlur" /> |
||||||
|
<feMerge> |
||||||
|
<feMergeNode in="coloredBlur" /> |
||||||
|
<feMergeNode in="SourceGraphic" /> |
||||||
|
</feMerge> |
||||||
|
</filter> |
||||||
|
</defs> |
||||||
|
|
||||||
|
{/* Animated Leaf */} |
||||||
|
<g className="leaf-group" filter="url(#glow)"> |
||||||
|
{/* Main Leaf */} |
||||||
|
<path |
||||||
|
className="leaf" |
||||||
|
d="M30 10 C40 10, 45 20, 45 30 C45 40, 35 45, 30 45 C25 45, 15 40, 15 30 C15 20, 20 10, 30 10 Z" |
||||||
|
fill="url(#leafGradient)" |
||||||
|
> |
||||||
|
<animate |
||||||
|
attributeName="d" |
||||||
|
values=" |
||||||
|
M30 10 C40 10, 45 20, 45 30 C45 40, 35 45, 30 45 C25 45, 15 40, 15 30 C15 20, 20 10, 30 10 Z; |
||||||
|
M30 12 C42 12, 47 22, 47 30 C47 38, 37 43, 30 43 C23 43, 13 38, 13 30 C13 22, 18 12, 30 12 Z; |
||||||
|
M30 10 C40 10, 45 20, 45 30 C45 40, 35 45, 30 45 C25 45, 15 40, 15 30 C15 20, 20 10, 30 10 Z" |
||||||
|
dur="4s" |
||||||
|
repeatCount="indefinite" |
||||||
|
/> |
||||||
|
</path> |
||||||
|
|
||||||
|
{/* Leaf Vein */} |
||||||
|
<path |
||||||
|
className="vein" |
||||||
|
d="M30 15 L30 40" |
||||||
|
stroke="#2f855a" |
||||||
|
strokeWidth="1.5" |
||||||
|
strokeLinecap="round" |
||||||
|
opacity="0.6" |
||||||
|
> |
||||||
|
<animate |
||||||
|
attributeName="d" |
||||||
|
values=" |
||||||
|
M30 15 L30 40; |
||||||
|
M30 17 L30 38; |
||||||
|
M30 15 L30 40" |
||||||
|
dur="4s" |
||||||
|
repeatCount="indefinite" |
||||||
|
/> |
||||||
|
</path> |
||||||
|
|
||||||
|
{/* Side Veins */} |
||||||
|
<g className="side-veins"> |
||||||
|
{[1, 2, 3].map((i) => ( |
||||||
|
<React.Fragment key={i}> |
||||||
|
<path |
||||||
|
d={`M30 ${20 + i * 7} L${38 - i * 2} ${25 + i * 5}`} |
||||||
|
stroke="#2f855a" |
||||||
|
strokeWidth="1" |
||||||
|
strokeLinecap="round" |
||||||
|
opacity="0.4" |
||||||
|
> |
||||||
|
<animate |
||||||
|
attributeName="opacity" |
||||||
|
values="0.4;0.6;0.4" |
||||||
|
dur="3s" |
||||||
|
begin={`${i * 0.5}s`} |
||||||
|
repeatCount="indefinite" |
||||||
|
/> |
||||||
|
</path> |
||||||
|
<path |
||||||
|
d={`M30 ${20 + i * 7} L${22 + i * 2} ${25 + i * 5}`} |
||||||
|
stroke="#2f855a" |
||||||
|
strokeWidth="1" |
||||||
|
strokeLinecap="round" |
||||||
|
opacity="0.4" |
||||||
|
> |
||||||
|
<animate |
||||||
|
attributeName="opacity" |
||||||
|
values="0.4;0.6;0.4" |
||||||
|
dur="3s" |
||||||
|
begin={`${i * 0.5}s`} |
||||||
|
repeatCount="indefinite" |
||||||
|
/> |
||||||
|
</path> |
||||||
|
</React.Fragment> |
||||||
|
))} |
||||||
|
</g> |
||||||
|
|
||||||
|
{/* Sparkle Effects */} |
||||||
|
<circle |
||||||
|
cx="35" |
||||||
|
cy="20" |
||||||
|
r="1" |
||||||
|
fill="#fff" |
||||||
|
opacity="0.6" |
||||||
|
> |
||||||
|
<animate |
||||||
|
attributeName="opacity" |
||||||
|
values="0.6;0;0.6" |
||||||
|
dur="2s" |
||||||
|
repeatCount="indefinite" |
||||||
|
/> |
||||||
|
</circle> |
||||||
|
</g> |
||||||
|
|
||||||
|
{/* Text */} |
||||||
|
<text |
||||||
|
x="60" |
||||||
|
y="42" |
||||||
|
className="logo-text" |
||||||
|
fill={isFooter ? "#FFFFFF" : "#2D3748"} |
||||||
|
style={{ |
||||||
|
fontSize: '32px', |
||||||
|
fontWeight: 'bold', |
||||||
|
fontFamily: 'system-ui, -apple-system, sans-serif' |
||||||
|
}} |
||||||
|
> |
||||||
|
<tspan className="eco" fill={isFooter ? "#FFFFFF" : "#48bb78"}>Eco</tspan> |
||||||
|
<tspan>Connect</tspan> |
||||||
|
</text> |
||||||
|
</svg> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default Logo; |
@ -0,0 +1,166 @@ |
|||||||
|
import React, { useEffect, useState } from 'react'; |
||||||
|
import { Link } from 'react-router-dom'; |
||||||
|
import {
|
||||||
|
Home, |
||||||
|
Info, |
||||||
|
Briefcase, |
||||||
|
Mail, |
||||||
|
Heart, |
||||||
|
Menu, |
||||||
|
UserPlus, |
||||||
|
LogIn, |
||||||
|
LogOut, |
||||||
|
ClipboardCheck, |
||||||
|
Users |
||||||
|
} from 'lucide-react'; |
||||||
|
import Logo from './Logo'; |
||||||
|
import DonateModal from './DonateModal'; |
||||||
|
|
||||||
|
const Navbar = () => { |
||||||
|
const [scrolled, setScrolled] = useState(false); |
||||||
|
const [isMenuOpen, setIsMenuOpen] = useState(false); |
||||||
|
const [isLoggedIn, setIsLoggedIn] = useState(() => { |
||||||
|
return sessionStorage.getItem('isLoggedIn') === 'true'; |
||||||
|
}); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
const handleStorageChange = () => { |
||||||
|
setIsLoggedIn(sessionStorage.getItem('isLoggedIn') === 'true'); |
||||||
|
}; |
||||||
|
|
||||||
|
window.addEventListener('storage', handleStorageChange); |
||||||
|
return () => window.removeEventListener('storage', handleStorageChange); |
||||||
|
}, []); |
||||||
|
|
||||||
|
const handleLogout = () => { |
||||||
|
sessionStorage.removeItem('isLoggedIn'); |
||||||
|
setIsLoggedIn(false); |
||||||
|
window.location.reload(); |
||||||
|
}; |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
const handleScroll = () => { |
||||||
|
const isScrolled = window.scrollY > 20; |
||||||
|
if (isScrolled !== scrolled) { |
||||||
|
setScrolled(isScrolled); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
window.addEventListener('scroll', handleScroll); |
||||||
|
return () => window.removeEventListener('scroll', handleScroll); |
||||||
|
}, [scrolled]); |
||||||
|
|
||||||
|
const toggleMenu = () => { |
||||||
|
setIsMenuOpen(!isMenuOpen); |
||||||
|
}; |
||||||
|
|
||||||
|
const navItems = [ |
||||||
|
{ path: '/', label: 'Home', icon: Home }, |
||||||
|
{ path: '/about', label: 'About', icon: Info }, |
||||||
|
{ path: '/projects', label: 'Projects', icon: Briefcase }, |
||||||
|
{ path: '/contact', label: 'Contact', icon: Mail } |
||||||
|
]; |
||||||
|
|
||||||
|
const volunteerItems = [ |
||||||
|
{ path: '/volunteer', label: 'Volunteer Registration', icon: UserPlus }, |
||||||
|
{ path: '/volunteer/acknowledgment', label: 'Volunteer List', icon: Users } |
||||||
|
]; |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<nav className={`navbar navbar-expand-lg navbar-light ${scrolled ? 'scrolled' : ''}`}> |
||||||
|
<div className="container"> |
||||||
|
<Link className="navbar-brand d-flex align-items-center" to="/"> |
||||||
|
<Logo height={32} className="navbar-logo me-2" /> |
||||||
|
</Link> |
||||||
|
|
||||||
|
<button |
||||||
|
className="navbar-toggler border-0 p-0" |
||||||
|
type="button" |
||||||
|
onClick={toggleMenu} |
||||||
|
aria-controls="navbarNav" |
||||||
|
aria-expanded={isMenuOpen} |
||||||
|
aria-label="Toggle navigation" |
||||||
|
> |
||||||
|
<Menu size={24} className="text-success" /> |
||||||
|
</button> |
||||||
|
|
||||||
|
<div className={`collapse navbar-collapse ${isMenuOpen ? 'show' : ''}`} id="navbarNav"> |
||||||
|
<ul className="navbar-nav me-auto d-flex align-items-center"> |
||||||
|
{navItems.map((item, index) => ( |
||||||
|
<li key={item.path} className="nav-item nav-item-animated" style={{ animationDelay: `${index * 0.1}s` }}> |
||||||
|
<Link
|
||||||
|
className="nav-link d-flex align-items-center"
|
||||||
|
to={item.path} |
||||||
|
onClick={() => setIsMenuOpen(false)} |
||||||
|
> |
||||||
|
<item.icon className="nav-icon me-2" size={16} /> |
||||||
|
<span className="nav-text">{item.label}</span> |
||||||
|
</Link> |
||||||
|
</li> |
||||||
|
))} |
||||||
|
|
||||||
|
<li className="nav-item dropdown"> |
||||||
|
<a |
||||||
|
className="nav-link dropdown-toggle d-flex align-items-center" |
||||||
|
href="#" |
||||||
|
role="button" |
||||||
|
data-bs-toggle="dropdown" |
||||||
|
aria-expanded="false" |
||||||
|
> |
||||||
|
<UserPlus className="nav-icon me-2" size={16} /> |
||||||
|
<span className="nav-text">Volunteer</span> |
||||||
|
</a> |
||||||
|
<ul className="dropdown-menu"> |
||||||
|
{volunteerItems.map((item) => ( |
||||||
|
<li key={item.path}> |
||||||
|
<Link |
||||||
|
className="dropdown-item d-flex align-items-center" |
||||||
|
to={item.path} |
||||||
|
onClick={() => setIsMenuOpen(false)} |
||||||
|
> |
||||||
|
<item.icon className="me-2" size={16} /> |
||||||
|
{item.label} |
||||||
|
</Link> |
||||||
|
</li> |
||||||
|
))} |
||||||
|
</ul> |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
|
||||||
|
<div className="nav-end"> |
||||||
|
{!isLoggedIn ? ( |
||||||
|
<Link
|
||||||
|
to="/login"
|
||||||
|
className="btn btn-outline-success me-2 d-flex align-items-center" |
||||||
|
> |
||||||
|
<LogIn className="me-2" size={16} /> |
||||||
|
<span>Sign In</span> |
||||||
|
</Link> |
||||||
|
) : ( |
||||||
|
<button
|
||||||
|
onClick={handleLogout} |
||||||
|
className="btn btn-outline-danger me-2 d-flex align-items-center" |
||||||
|
> |
||||||
|
<LogOut className="me-2" size={16} /> |
||||||
|
<span>Logout</span> |
||||||
|
</button> |
||||||
|
)} |
||||||
|
<button
|
||||||
|
className="btn btn-success btn-donate d-flex align-items-center" |
||||||
|
data-bs-toggle="modal" |
||||||
|
data-bs-target="#donateModal" |
||||||
|
> |
||||||
|
<Heart className="me-2" size={16} /> |
||||||
|
<span>Donate</span> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</nav> |
||||||
|
<DonateModal /> |
||||||
|
</> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default Navbar; |
@ -0,0 +1,114 @@ |
|||||||
|
import React, { useState, useEffect } from 'react'; |
||||||
|
import { Newspaper, ExternalLink, Loader2 } from 'lucide-react'; |
||||||
|
|
||||||
|
interface NewsArticle { |
||||||
|
title: string; |
||||||
|
description: string; |
||||||
|
url: string; |
||||||
|
urlToImage: string; |
||||||
|
publishedAt: string; |
||||||
|
source: { |
||||||
|
name: string; |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
// Fallback news data
|
||||||
|
const fallbackNews: NewsArticle[] = [ |
||||||
|
{ |
||||||
|
title: "Urban Gardens Transform Communities", |
||||||
|
description: "Local initiatives are turning unused urban spaces into thriving community gardens, promoting sustainability and food security.", |
||||||
|
url: "#", |
||||||
|
urlToImage: "https://images.unsplash.com/photo-1466692476868-aef1dfb1e735", |
||||||
|
publishedAt: new Date().toISOString(), |
||||||
|
source: { name: "EcoConnect" } |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: "Innovative Recycling Programs Show Promise", |
||||||
|
description: "New recycling technologies and community programs are making significant strides in reducing waste and promoting circular economy.", |
||||||
|
url: "#", |
||||||
|
urlToImage: "https://images.unsplash.com/photo-1532996122724-e3c354a0b15b", |
||||||
|
publishedAt: new Date().toISOString(), |
||||||
|
source: { name: "EcoConnect" } |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: "Youth Environmental Leaders Rise", |
||||||
|
description: "Young activists are leading the charge in environmental conservation, organizing successful community cleanup and awareness campaigns.", |
||||||
|
url: "#", |
||||||
|
urlToImage: "https://images.unsplash.com/photo-1542601906990-b4d3fb778b09", |
||||||
|
publishedAt: new Date().toISOString(), |
||||||
|
source: { name: "EcoConnect" } |
||||||
|
} |
||||||
|
]; |
||||||
|
const News = () => { |
||||||
|
const [articles, setArticles] = useState<NewsArticle[]>([]); |
||||||
|
const [loading, setLoading] = useState(true); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
// Simulate loading
|
||||||
|
setTimeout(() => { |
||||||
|
setArticles(fallbackNews); |
||||||
|
setLoading(false); |
||||||
|
}, 1000); |
||||||
|
}, []); |
||||||
|
|
||||||
|
if (loading) { |
||||||
|
return ( |
||||||
|
<div className="text-center py-5"> |
||||||
|
<Loader2 className="animate-spin h-8 w-8 text-success mx-auto" /> |
||||||
|
<p className="mt-2">Loading news...</p> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="news-section py-5"> |
||||||
|
<div className="container"> |
||||||
|
<div className="text-center mb-5"> |
||||||
|
<h2 className="display-4 mb-3"> |
||||||
|
<Newspaper className="inline-block mr-2 text-success" /> |
||||||
|
Latest Updates |
||||||
|
</h2> |
||||||
|
<p className="lead text-muted">Stay updated with our latest environmental initiatives and success stories</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="row g-4"> |
||||||
|
{articles.map((article, index) => ( |
||||||
|
<div key={index} className="col-md-6 col-lg-4"> |
||||||
|
<div className="card h-100 border-0 shadow-sm hover-card"> |
||||||
|
{article.urlToImage && ( |
||||||
|
<img |
||||||
|
src={article.urlToImage} |
||||||
|
alt={article.title} |
||||||
|
className="card-img-top" |
||||||
|
style={{ height: '200px', objectFit: 'cover' }} |
||||||
|
/> |
||||||
|
)} |
||||||
|
<div className="card-body"> |
||||||
|
<h5 className="card-title mb-3">{article.title}</h5> |
||||||
|
<p className="card-text text-muted"> |
||||||
|
{article.description?.substring(0, 150)}... |
||||||
|
</p> |
||||||
|
<div className="d-flex justify-content-between align-items-center mt-3"> |
||||||
|
<small className="text-muted"> |
||||||
|
{new Date(article.publishedAt).toLocaleDateString()} |
||||||
|
</small> |
||||||
|
<a |
||||||
|
href={article.url} |
||||||
|
target="_blank" |
||||||
|
rel="noopener noreferrer" |
||||||
|
className="btn btn-outline-success btn-sm" |
||||||
|
> |
||||||
|
Read More <ExternalLink size={14} className="ms-1" /> |
||||||
|
</a> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
))} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default News; |
@ -0,0 +1,68 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { TreePine, Recycle, Droplets } from 'lucide-react'; |
||||||
|
|
||||||
|
const Projects = () => { |
||||||
|
const projects = [ |
||||||
|
{ |
||||||
|
title: "Food Rescue Program", |
||||||
|
description: "Connect with local restaurants and supermarkets to rescue surplus food and redistribute it to those in need.", |
||||||
|
image: "https://images.unsplash.com/photo-1488459716781-31db52582fe9", |
||||||
|
icon: <TreePine className="text-success" size={24} />, |
||||||
|
participants: 150, |
||||||
|
location: "Multiple Cities" |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: "Food Safety Training", |
||||||
|
description: "Comprehensive training program for volunteers on food safety, handling, and transportation protocols.", |
||||||
|
image: "https://images.unsplash.com/photo-1532996122724-e3c354a0b15b", |
||||||
|
icon: <Recycle className="text-success" size={24} />, |
||||||
|
participants: 300, |
||||||
|
location: "Nationwide" |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: "Community Kitchen Network", |
||||||
|
description: "Partnership program with local kitchens to process and distribute rescued food to shelters and food banks.", |
||||||
|
image: "https://images.unsplash.com/photo-1517433670267-08bbd4be890f", |
||||||
|
icon: <Droplets className="text-success" size={24} />, |
||||||
|
participants: 200, |
||||||
|
location: "Regional" |
||||||
|
} |
||||||
|
]; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="projects py-5"> |
||||||
|
<div className="container"> |
||||||
|
<div className="text-center mb-5"> |
||||||
|
<h1 className="display-4 mb-3">Our Projects</h1> |
||||||
|
<p className="lead">Join our food rescue initiatives and help reduce waste while supporting communities in need.</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="row g-4"> |
||||||
|
{projects.map((project, index) => ( |
||||||
|
<div key={index} className="col-md-4"> |
||||||
|
<div className="card h-100 border-0 shadow-sm"> |
||||||
|
<img src={project.image} className="card-img-top" alt={project.title} style={{height: "200px", objectFit: "cover"}} /> |
||||||
|
<div className="card-body"> |
||||||
|
<div className="d-flex align-items-center mb-3"> |
||||||
|
{project.icon} |
||||||
|
<h3 className="h5 mb-0 ms-2">{project.title}</h3> |
||||||
|
</div> |
||||||
|
<p className="card-text">{project.description}</p> |
||||||
|
<div className="d-flex justify-content-between align-items-center"> |
||||||
|
<small className="text-muted">{project.participants} Participants</small> |
||||||
|
<small className="text-muted">{project.location}</small> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="card-footer bg-transparent border-0"> |
||||||
|
<button className="btn btn-success w-100">Join Project</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
))} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default Projects; |
@ -0,0 +1,61 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html lang="en"> |
||||||
|
<head> |
||||||
|
<meta charset="UTF-8"> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||||
|
<title>Sign Up - EcoConnect</title> |
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> |
||||||
|
<link href="auth.css" rel="stylesheet"> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div class="auth-container"> |
||||||
|
<div class="auth-box"> |
||||||
|
<div class="auth-header"> |
||||||
|
<h2>Create Account</h2> |
||||||
|
<p>Join our community today</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<form class="auth-form"> |
||||||
|
<div class="form-group"> |
||||||
|
<label for="name">Full Name</label> |
||||||
|
<input type="text" id="name" class="form-control" placeholder="Enter your full name"> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="form-group"> |
||||||
|
<input type="email" id="email" class="form-control" placeholder="Enter your email"> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="form-group"> |
||||||
|
<label for="password">Password</label> |
||||||
|
<input type="password" id="password" class="form-control" placeholder="Create a password"> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="form-group"> |
||||||
|
<label for="confirm-password">Confirm Password</label> |
||||||
|
<input type="password" id="confirm-password" class="form-control" placeholder="Confirm your password"> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="terms"> |
||||||
|
<input type="checkbox" id="terms"> |
||||||
|
<label for="terms">I agree to the <a href="#">Terms of Service</a> and <a href="#">Privacy Policy</a></label> |
||||||
|
</div> |
||||||
|
|
||||||
|
<button type="submit" class="auth-button">Create Account</button> |
||||||
|
|
||||||
|
<div class="auth-divider"> |
||||||
|
<span>OR</span> |
||||||
|
</div> |
||||||
|
|
||||||
|
<button type="button" class="social-button google"> |
||||||
|
<img src="https://upload.wikimedia.org/wikipedia/commons/5/53/Google_%22G%22_Logo.svg" alt="Google"> |
||||||
|
Sign up with Google |
||||||
|
</button> |
||||||
|
|
||||||
|
<p class="auth-footer"> |
||||||
|
Already have an account? <a href="login.html">Sign in</a> |
||||||
|
</p> |
||||||
|
</form> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,237 @@ |
|||||||
|
import React, { useState } from 'react'; |
||||||
|
import { useNavigate } from 'react-router-dom'; |
||||||
|
import { Mail, Lock, User, Eye, EyeOff } from 'lucide-react'; |
||||||
|
import Logo from './Logo'; |
||||||
|
|
||||||
|
interface FormData { |
||||||
|
username: string; |
||||||
|
email: string; |
||||||
|
password: string; |
||||||
|
confirmPassword: string; |
||||||
|
rememberDevice: boolean; |
||||||
|
} |
||||||
|
|
||||||
|
const Signup = () => { |
||||||
|
const navigate = useNavigate(); |
||||||
|
const [formData, setFormData] = useState<FormData>({ |
||||||
|
username: '', |
||||||
|
email: '', |
||||||
|
password: '', |
||||||
|
confirmPassword: '', |
||||||
|
rememberDevice: false |
||||||
|
}); |
||||||
|
|
||||||
|
const [showNotification, setShowNotification] = useState(false); |
||||||
|
const [showPassword, setShowPassword] = useState(false); |
||||||
|
const [showConfirmPassword, setShowConfirmPassword] = useState(false); |
||||||
|
const [error, setError] = useState(''); |
||||||
|
const [loading, setLoading] = useState(false); |
||||||
|
|
||||||
|
const showSuccessAndRedirect = () => { |
||||||
|
setShowNotification(true); |
||||||
|
setTimeout(() => { |
||||||
|
navigate('/'); |
||||||
|
}, 5000); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { |
||||||
|
const { id, value, type, checked } = e.target; |
||||||
|
setFormData(prev => ({ |
||||||
|
...prev, |
||||||
|
[id]: type === 'checkbox' ? checked : value |
||||||
|
})); |
||||||
|
setError(''); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => { |
||||||
|
e.preventDefault(); |
||||||
|
|
||||||
|
if (formData.password !== formData.confirmPassword) { |
||||||
|
setError('Passwords do not match'); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (formData.password.length < 8) { |
||||||
|
setError('Password must be at least 8 characters long'); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
setLoading(true); |
||||||
|
setError(''); |
||||||
|
|
||||||
|
try { |
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000)); |
||||||
|
|
||||||
|
// Set login status after successful signup
|
||||||
|
sessionStorage.setItem('isLoggedIn', 'true'); |
||||||
|
|
||||||
|
// Show success notification and redirect
|
||||||
|
showSuccessAndRedirect(); |
||||||
|
} finally { |
||||||
|
setLoading(false); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="signup-page"> |
||||||
|
<div className="container py-5"> |
||||||
|
<div className="row justify-content-center"> |
||||||
|
{showNotification && ( |
||||||
|
<div className="position-fixed top-0 start-50 translate-middle-x p-3" style={{ zIndex: 1050 }}> |
||||||
|
<div className="toast show bg-success text-white" role="alert" style={{ |
||||||
|
minWidth: '300px', |
||||||
|
borderRadius: '10px', |
||||||
|
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)' |
||||||
|
}}> |
||||||
|
<div className="toast-body text-center py-3"> |
||||||
|
<strong>✨ Signed up successfully! Redirecting to home page in 5 seconds...</strong> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
)} |
||||||
|
<div className="col-md-6 col-lg-5"> |
||||||
|
<div className="card border-0 shadow-lg signup-card"> |
||||||
|
<div className="card-body p-4 p-md-5"> |
||||||
|
<div className="text-center mb-4"> |
||||||
|
<Logo height={40} className="mb-4" /> |
||||||
|
<h2 className="signup-title">Create Account</h2> |
||||||
|
<p className="signup-subtitle text-muted">Join our community today</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
{error && ( |
||||||
|
<div className="alert alert-danger mb-4" role="alert"> |
||||||
|
{error} |
||||||
|
</div> |
||||||
|
)} |
||||||
|
|
||||||
|
<form onSubmit={handleSubmit}> |
||||||
|
<div className="mb-3"> |
||||||
|
<div className="input-group"> |
||||||
|
<span className="input-group-text bg-light border-end-0"> |
||||||
|
<User className="text-muted" size={18} /> |
||||||
|
</span> |
||||||
|
<input |
||||||
|
type="text" |
||||||
|
className="form-control border-start-0" |
||||||
|
id="username" |
||||||
|
placeholder="Username" |
||||||
|
value={formData.username} |
||||||
|
onChange={handleChange} |
||||||
|
required |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="mb-3"> |
||||||
|
<div className="input-group"> |
||||||
|
<span className="input-group-text bg-light border-end-0"> |
||||||
|
<Mail className="text-muted" size={18} /> |
||||||
|
</span> |
||||||
|
<input |
||||||
|
type="email" |
||||||
|
className="form-control border-start-0" |
||||||
|
id="email" |
||||||
|
placeholder="Email address" |
||||||
|
value={formData.email} |
||||||
|
onChange={handleChange} |
||||||
|
required |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="mb-3"> |
||||||
|
<div className="input-group"> |
||||||
|
<span className="input-group-text bg-light border-end-0"> |
||||||
|
<Lock className="text-muted" size={18} /> |
||||||
|
</span> |
||||||
|
<input |
||||||
|
type={showPassword ? "text" : "password"} |
||||||
|
className="form-control border-start-0 border-end-0" |
||||||
|
id="password" |
||||||
|
placeholder="Password" |
||||||
|
value={formData.password} |
||||||
|
onChange={handleChange} |
||||||
|
required |
||||||
|
minLength={8} |
||||||
|
/> |
||||||
|
<button |
||||||
|
type="button" |
||||||
|
className="input-group-text bg-light border-start-0" |
||||||
|
onClick={() => setShowPassword(!showPassword)} |
||||||
|
> |
||||||
|
{showPassword ?
|
||||||
|
<EyeOff className="text-muted" size={18} /> :
|
||||||
|
<Eye className="text-muted" size={18} /> |
||||||
|
} |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="mb-3"> |
||||||
|
<div className="input-group"> |
||||||
|
<span className="input-group-text bg-light border-end-0"> |
||||||
|
<Lock className="text-muted" size={18} /> |
||||||
|
</span> |
||||||
|
<input |
||||||
|
type={showConfirmPassword ? "text" : "password"} |
||||||
|
className="form-control border-start-0 border-end-0" |
||||||
|
id="confirmPassword" |
||||||
|
placeholder="Confirm Password" |
||||||
|
value={formData.confirmPassword} |
||||||
|
onChange={handleChange} |
||||||
|
required |
||||||
|
minLength={8} |
||||||
|
/> |
||||||
|
<button |
||||||
|
type="button" |
||||||
|
className="input-group-text bg-light border-start-0" |
||||||
|
onClick={() => setShowConfirmPassword(!showConfirmPassword)} |
||||||
|
> |
||||||
|
{showConfirmPassword ?
|
||||||
|
<EyeOff className="text-muted" size={18} /> :
|
||||||
|
<Eye className="text-muted" size={18} /> |
||||||
|
} |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="form-check mb-4"> |
||||||
|
<input |
||||||
|
type="checkbox" |
||||||
|
className="form-check-input" |
||||||
|
id="rememberDevice" |
||||||
|
checked={formData.rememberDevice} |
||||||
|
onChange={handleChange} |
||||||
|
/> |
||||||
|
<label className="form-check-label text-muted" htmlFor="rememberDevice"> |
||||||
|
Remember this device |
||||||
|
</label> |
||||||
|
</div> |
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-success w-100 mb-3" |
||||||
|
disabled={loading} |
||||||
|
> |
||||||
|
{loading ? 'Creating Account...' : 'Create Account'} |
||||||
|
</button> |
||||||
|
|
||||||
|
<div className="text-center"> |
||||||
|
<p className="text-muted mb-0"> |
||||||
|
Already have an account?{' '} |
||||||
|
<a href="/login" className="text-success fw-semibold"> |
||||||
|
Sign in |
||||||
|
</a> |
||||||
|
</p> |
||||||
|
</div> |
||||||
|
</form> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default Signup; |
@ -0,0 +1,46 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { LogOut, User } from 'lucide-react'; |
||||||
|
import { useAuth } from '../context/AuthContext'; |
||||||
|
|
||||||
|
const UserMenu = () => { |
||||||
|
const { user, logout } = useAuth(); |
||||||
|
|
||||||
|
if (!user) return null; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="dropdown"> |
||||||
|
<button |
||||||
|
className="btn btn-link text-dark text-decoration-none dropdown-toggle d-flex align-items-center" |
||||||
|
type="button" |
||||||
|
data-bs-toggle="dropdown" |
||||||
|
aria-expanded="false" |
||||||
|
> |
||||||
|
{user.avatar ? ( |
||||||
|
<img |
||||||
|
src={user.avatar} |
||||||
|
alt={user.name} |
||||||
|
className="rounded-circle me-2" |
||||||
|
width="32" |
||||||
|
height="32" |
||||||
|
/> |
||||||
|
) : ( |
||||||
|
<User className="me-2" size={24} /> |
||||||
|
)} |
||||||
|
{user.name} |
||||||
|
</button> |
||||||
|
<ul className="dropdown-menu dropdown-menu-end"> |
||||||
|
<li> |
||||||
|
<button |
||||||
|
className="dropdown-item text-danger d-flex align-items-center" |
||||||
|
onClick={logout} |
||||||
|
> |
||||||
|
<LogOut className="me-2" size={18} /> |
||||||
|
Sign Out |
||||||
|
</button> |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default UserMenu; |
@ -0,0 +1,119 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html lang="en"> |
||||||
|
<head> |
||||||
|
<meta charset="UTF-8"> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||||
|
<title>Volunteer Registration - EcoConnect</title> |
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> |
||||||
|
<link href="../styles/volunteer.css" rel="stylesheet"> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div class="volunteer-container"> |
||||||
|
<div class="volunteer-card"> |
||||||
|
<div class="volunteer-header"> |
||||||
|
<i class="icon heart-icon"></i> |
||||||
|
<h1>Volunteer Registration</h1> |
||||||
|
<p>Join our community and make a difference</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<form class="volunteer-form"> |
||||||
|
<div class="form-group"> |
||||||
|
<label> |
||||||
|
<i class="icon user-icon"></i> |
||||||
|
Full Name |
||||||
|
</label> |
||||||
|
<input type="text" class="form-control" placeholder="Enter your full name" required> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="form-group"> |
||||||
|
<label> |
||||||
|
<i class="icon mail-icon"></i> |
||||||
|
Email |
||||||
|
</label> |
||||||
|
<input type="email" class="form-control" placeholder="Enter your email" required> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="form-group"> |
||||||
|
<label> |
||||||
|
<i class="icon phone-icon"></i> |
||||||
|
Phone |
||||||
|
</label> |
||||||
|
<input type="tel" class="form-control" placeholder="Enter your phone number" required> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="form-group"> |
||||||
|
<label> |
||||||
|
<i class="icon map-icon"></i> |
||||||
|
Address |
||||||
|
</label> |
||||||
|
<textarea class="form-control" placeholder="Enter your address" rows="3" required></textarea> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="form-group"> |
||||||
|
<label> |
||||||
|
<i class="icon building-icon"></i> |
||||||
|
Organization (Optional) |
||||||
|
</label> |
||||||
|
<input type="text" class="form-control" placeholder="Enter organization name if applicable"> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="form-group"> |
||||||
|
<label> |
||||||
|
<i class="icon heart-icon"></i> |
||||||
|
Areas of Interest |
||||||
|
</label> |
||||||
|
<div class="checkbox-group"> |
||||||
|
<label class="checkbox-label"> |
||||||
|
<input type="checkbox" name="interests" value="environment"> |
||||||
|
<span>Environment</span> |
||||||
|
</label> |
||||||
|
<label class="checkbox-label"> |
||||||
|
<input type="checkbox" name="interests" value="education"> |
||||||
|
<span>Education</span> |
||||||
|
</label> |
||||||
|
<label class="checkbox-label"> |
||||||
|
<input type="checkbox" name="interests" value="community"> |
||||||
|
<span>Community</span> |
||||||
|
</label> |
||||||
|
<label class="checkbox-label"> |
||||||
|
<input type="checkbox" name="interests" value="events"> |
||||||
|
<span>Events</span> |
||||||
|
</label> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="form-group"> |
||||||
|
<label> |
||||||
|
<i class="icon calendar-icon"></i> |
||||||
|
Availability |
||||||
|
</label> |
||||||
|
<div class="checkbox-group"> |
||||||
|
<label class="checkbox-label"> |
||||||
|
<input type="checkbox" name="availability" value="weekdays"> |
||||||
|
<span>Weekdays</span> |
||||||
|
</label> |
||||||
|
<label class="checkbox-label"> |
||||||
|
<input type="checkbox" name="availability" value="weekends"> |
||||||
|
<span>Weekends</span> |
||||||
|
</label> |
||||||
|
<label class="checkbox-label"> |
||||||
|
<input type="checkbox" name="availability" value="evenings"> |
||||||
|
<span>Evenings</span> |
||||||
|
</label> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="form-group"> |
||||||
|
<label> |
||||||
|
<i class="icon user-icon"></i> |
||||||
|
Experience |
||||||
|
</label> |
||||||
|
<textarea class="form-control" placeholder="Tell us about your relevant experience" rows="4"></textarea> |
||||||
|
</div> |
||||||
|
|
||||||
|
<button type="submit" class="submit-button">Register as Volunteer</button> |
||||||
|
</form> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,246 @@ |
|||||||
|
import React, { useState } from 'react'; |
||||||
|
import { User, Mail, Phone, MapPin, Building, Calendar, Heart } from 'lucide-react'; |
||||||
|
import '../styles/volunteer.css'; |
||||||
|
|
||||||
|
interface VolunteerForm { |
||||||
|
fullName: string; |
||||||
|
email: string; |
||||||
|
phone: string; |
||||||
|
address: string; |
||||||
|
organization: string; |
||||||
|
interests: string[]; |
||||||
|
availability: string[]; |
||||||
|
experience: string; |
||||||
|
} |
||||||
|
|
||||||
|
const Volunteer = () => { |
||||||
|
const [formData, setFormData] = useState<VolunteerForm>({ |
||||||
|
fullName: '', |
||||||
|
email: '', |
||||||
|
phone: '', |
||||||
|
address: '', |
||||||
|
organization: '', |
||||||
|
interests: [], |
||||||
|
availability: [], |
||||||
|
experience: '' |
||||||
|
}); |
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false); |
||||||
|
const [success, setSuccess] = useState(''); |
||||||
|
const [error, setError] = useState(''); |
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => { |
||||||
|
setFormData({ |
||||||
|
...formData, |
||||||
|
[e.target.name]: e.target.value |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleCheckboxChange = (e: React.ChangeEvent<HTMLInputElement>) => { |
||||||
|
const { name, value, checked } = e.target; |
||||||
|
setFormData(prev => ({ |
||||||
|
...prev, |
||||||
|
[name]: checked
|
||||||
|
? [...prev[name as keyof VolunteerForm] as string[], value] |
||||||
|
: (prev[name as keyof VolunteerForm] as string[]).filter(item => item !== value) |
||||||
|
})); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => { |
||||||
|
e.preventDefault(); |
||||||
|
setLoading(true); |
||||||
|
setError(''); |
||||||
|
setSuccess(''); |
||||||
|
|
||||||
|
try { |
||||||
|
// Simulate form submission
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000)); |
||||||
|
console.log('Form submitted:', formData); |
||||||
|
|
||||||
|
setSuccess('Thank you for registering! We will contact you soon.'); |
||||||
|
setFormData({ |
||||||
|
fullName: '', |
||||||
|
email: '', |
||||||
|
phone: '', |
||||||
|
address: '', |
||||||
|
organization: '', |
||||||
|
interests: [], |
||||||
|
availability: [], |
||||||
|
experience: '' |
||||||
|
}); |
||||||
|
} catch (err) { |
||||||
|
setError('An error occurred. Please try again.'); |
||||||
|
console.error('Registration error:', err); |
||||||
|
} finally { |
||||||
|
setLoading(false); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="volunteer-container"> |
||||||
|
<div className="volunteer-card"> |
||||||
|
<div className="volunteer-header"> |
||||||
|
<Heart className="volunteer-icon" size={40} /> |
||||||
|
<h1>Volunteer Registration</h1> |
||||||
|
<p>Join our community and make a difference</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
{success && ( |
||||||
|
<div className="alert alert-success" role="alert"> |
||||||
|
{success} |
||||||
|
</div> |
||||||
|
)} |
||||||
|
|
||||||
|
{error && ( |
||||||
|
<div className="alert alert-danger" role="alert"> |
||||||
|
{error} |
||||||
|
</div> |
||||||
|
)} |
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className="volunteer-form"> |
||||||
|
<div className="form-group"> |
||||||
|
<label> |
||||||
|
<User className="icon" /> |
||||||
|
Full Name |
||||||
|
</label> |
||||||
|
<input |
||||||
|
type="text" |
||||||
|
name="fullName" |
||||||
|
value={formData.fullName} |
||||||
|
onChange={handleChange} |
||||||
|
required |
||||||
|
className="form-control" |
||||||
|
placeholder="Enter your full name" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="form-group"> |
||||||
|
<label> |
||||||
|
<Mail className="icon" /> |
||||||
|
Email |
||||||
|
</label> |
||||||
|
<input |
||||||
|
type="email" |
||||||
|
name="email" |
||||||
|
value={formData.email} |
||||||
|
onChange={handleChange} |
||||||
|
required |
||||||
|
className="form-control" |
||||||
|
placeholder="Enter your email" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="form-group"> |
||||||
|
<label> |
||||||
|
<Phone className="icon" /> |
||||||
|
Phone |
||||||
|
</label> |
||||||
|
<input |
||||||
|
type="tel" |
||||||
|
name="phone" |
||||||
|
value={formData.phone} |
||||||
|
onChange={handleChange} |
||||||
|
required |
||||||
|
className="form-control" |
||||||
|
placeholder="Enter your phone number" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="form-group"> |
||||||
|
<label> |
||||||
|
<MapPin className="icon" /> |
||||||
|
Address |
||||||
|
</label> |
||||||
|
<textarea |
||||||
|
name="address" |
||||||
|
value={formData.address} |
||||||
|
onChange={handleChange} |
||||||
|
required |
||||||
|
className="form-control" |
||||||
|
placeholder="Enter your address" |
||||||
|
rows={3} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="form-group"> |
||||||
|
<label> |
||||||
|
<Building className="icon" /> |
||||||
|
Organization (Optional) |
||||||
|
</label> |
||||||
|
<input |
||||||
|
type="text" |
||||||
|
name="organization" |
||||||
|
value={formData.organization} |
||||||
|
onChange={handleChange} |
||||||
|
className="form-control" |
||||||
|
placeholder="Enter organization name if applicable" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="form-group"> |
||||||
|
<label> |
||||||
|
<Heart className="icon" /> |
||||||
|
Areas of Interest |
||||||
|
</label> |
||||||
|
<div className="checkbox-group"> |
||||||
|
{['Environment', 'Education', 'Community', 'Events'].map(interest => ( |
||||||
|
<label key={interest} className="checkbox-label"> |
||||||
|
<input |
||||||
|
type="checkbox" |
||||||
|
name="interests" |
||||||
|
value={interest.toLowerCase()} |
||||||
|
checked={formData.interests.includes(interest.toLowerCase())} |
||||||
|
onChange={handleCheckboxChange} |
||||||
|
/> |
||||||
|
<span>{interest}</span> |
||||||
|
</label> |
||||||
|
))} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="form-group"> |
||||||
|
<label> |
||||||
|
<Calendar className="icon" /> |
||||||
|
Availability |
||||||
|
</label> |
||||||
|
<div className="checkbox-group"> |
||||||
|
{['Weekdays', 'Weekends', 'Evenings'].map(time => ( |
||||||
|
<label key={time} className="checkbox-label"> |
||||||
|
<input |
||||||
|
type="checkbox" |
||||||
|
name="availability" |
||||||
|
value={time.toLowerCase()} |
||||||
|
checked={formData.availability.includes(time.toLowerCase())} |
||||||
|
onChange={handleCheckboxChange} |
||||||
|
/> |
||||||
|
<span>{time}</span> |
||||||
|
</label> |
||||||
|
))} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="form-group"> |
||||||
|
<label> |
||||||
|
<User className="icon" /> |
||||||
|
Experience |
||||||
|
</label> |
||||||
|
<textarea |
||||||
|
name="experience" |
||||||
|
value={formData.experience} |
||||||
|
onChange={handleChange} |
||||||
|
className="form-control" |
||||||
|
placeholder="Tell us about your relevant experience" |
||||||
|
rows={4} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<button type="submit" className="submit-button" disabled={loading}> |
||||||
|
{loading ? 'Submitting...' : 'Register as Volunteer'} |
||||||
|
</button> |
||||||
|
</form> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default Volunteer; |
@ -0,0 +1,175 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { Trophy, Building, Calendar, Star } from 'lucide-react'; |
||||||
|
|
||||||
|
interface Volunteer { |
||||||
|
id: number; |
||||||
|
fullName: string; |
||||||
|
organization: string; |
||||||
|
joinedDate: string; |
||||||
|
contributions: number; |
||||||
|
} |
||||||
|
|
||||||
|
const VolunteerAcknowledgment = () => { |
||||||
|
// Mock data for best volunteer
|
||||||
|
const bestVolunteer = { |
||||||
|
name: "Sarah Johnson", |
||||||
|
organization: "Green Earth Foundation", |
||||||
|
achievement: "Led 3 successful community cleanup events", |
||||||
|
impact: "Collected over 500kg of recyclable waste", |
||||||
|
image: "https://images.unsplash.com/photo-1573497019940-1c28c88b4f3e" |
||||||
|
}; |
||||||
|
|
||||||
|
// Mock data for volunteer list
|
||||||
|
const volunteers: Volunteer[] = [ |
||||||
|
{ |
||||||
|
id: 1, |
||||||
|
fullName: "Sarah Johnson", |
||||||
|
organization: "Green Earth Foundation", |
||||||
|
joinedDate: "2024-01-15", |
||||||
|
contributions: 12 |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: 2, |
||||||
|
fullName: "Michael Chen", |
||||||
|
organization: "EcoTech Solutions", |
||||||
|
joinedDate: "2024-02-01", |
||||||
|
contributions: 8 |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: 3, |
||||||
|
fullName: "Emma Wilson", |
||||||
|
organization: "", |
||||||
|
joinedDate: "2024-02-15", |
||||||
|
contributions: 6 |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: 4, |
||||||
|
fullName: "David Park", |
||||||
|
organization: "City Environmental Club", |
||||||
|
joinedDate: "2024-01-20", |
||||||
|
contributions: 10 |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: 5, |
||||||
|
fullName: "Lisa Rodriguez", |
||||||
|
organization: "", |
||||||
|
joinedDate: "2024-02-10", |
||||||
|
contributions: 7 |
||||||
|
} |
||||||
|
]; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="volunteer-acknowledgment py-5"> |
||||||
|
<div className="container"> |
||||||
|
{/* Best Volunteer of the Week Section */} |
||||||
|
<div className="text-center mb-5"> |
||||||
|
<h1 className="display-4 mb-3">Volunteer Recognition</h1> |
||||||
|
<p className="lead text-muted">Celebrating our outstanding community contributors</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="best-volunteer-card mb-5"> |
||||||
|
<div className="card border-0 shadow-lg"> |
||||||
|
<div className="row g-0"> |
||||||
|
<div className="col-md-4"> |
||||||
|
<img |
||||||
|
src={bestVolunteer.image} |
||||||
|
alt="Best Volunteer" |
||||||
|
className="img-fluid rounded-start h-100" |
||||||
|
style={{ objectFit: 'cover' }} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div className="col-md-8"> |
||||||
|
<div className="card-body p-4"> |
||||||
|
<div className="d-flex align-items-center mb-3"> |
||||||
|
<Trophy className="text-warning me-2" size={28} /> |
||||||
|
<h2 className="card-title h3 mb-0">Volunteer of the Week</h2> |
||||||
|
</div> |
||||||
|
<h3 className="h4 mb-2">{bestVolunteer.name}</h3> |
||||||
|
<p className="text-muted mb-3 d-flex align-items-center"> |
||||||
|
<Building className="me-2" size={16} /> |
||||||
|
{bestVolunteer.organization} |
||||||
|
</p> |
||||||
|
<div className="achievement-box bg-light p-3 rounded mb-3"> |
||||||
|
<h4 className="h6 mb-2">Key Achievement</h4> |
||||||
|
<p className="mb-0">{bestVolunteer.achievement}</p> |
||||||
|
</div> |
||||||
|
<div className="impact-box bg-success bg-opacity-10 p-3 rounded"> |
||||||
|
<h4 className="h6 mb-2">Environmental Impact</h4> |
||||||
|
<p className="mb-0">{bestVolunteer.impact}</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
{/* Volunteers Table Section */} |
||||||
|
<div className="volunteers-table-section"> |
||||||
|
<div className="card border-0 shadow-sm"> |
||||||
|
<div className="card-header bg-white py-3"> |
||||||
|
<h3 className="mb-0">Active Volunteers</h3> |
||||||
|
</div> |
||||||
|
<div className="card-body"> |
||||||
|
<div className="table-responsive"> |
||||||
|
<table className="table table-hover align-middle"> |
||||||
|
<thead className="bg-light"> |
||||||
|
<tr> |
||||||
|
<th>Full Name</th> |
||||||
|
<th>Organization</th> |
||||||
|
<th>Joined Date</th> |
||||||
|
<th>Contributions</th> |
||||||
|
<th>Status</th> |
||||||
|
</tr> |
||||||
|
</thead> |
||||||
|
<tbody> |
||||||
|
{volunteers.map((volunteer) => ( |
||||||
|
<tr key={volunteer.id}> |
||||||
|
<td> |
||||||
|
<div className="d-flex align-items-center"> |
||||||
|
{volunteer.id === 1 && ( |
||||||
|
<Star className="text-warning me-2" size={16} /> |
||||||
|
)} |
||||||
|
{volunteer.fullName} |
||||||
|
</div> |
||||||
|
</td> |
||||||
|
<td> |
||||||
|
{volunteer.organization || ( |
||||||
|
<span className="text-muted">Independent</span> |
||||||
|
)} |
||||||
|
</td> |
||||||
|
<td> |
||||||
|
<div className="d-flex align-items-center"> |
||||||
|
<Calendar className="me-2" size={16} /> |
||||||
|
{new Date(volunteer.joinedDate).toLocaleDateString()} |
||||||
|
</div> |
||||||
|
</td> |
||||||
|
<td> |
||||||
|
<div className="d-flex align-items-center"> |
||||||
|
<div className="me-2">{volunteer.contributions}</div> |
||||||
|
<div className="progress flex-grow-1" style={{ height: '6px', width: '100px' }}> |
||||||
|
<div |
||||||
|
className="progress-bar bg-success" |
||||||
|
style={{ width: `${(volunteer.contributions / 12) * 100}%` }} |
||||||
|
></div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</td> |
||||||
|
<td> |
||||||
|
<span className={`badge bg-${volunteer.contributions > 10 ? 'success' : 'primary'}`}> |
||||||
|
{volunteer.contributions > 10 ? 'Active' : 'Regular'} |
||||||
|
</span> |
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
))} |
||||||
|
</tbody> |
||||||
|
</table> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default VolunteerAcknowledgment; |
@ -0,0 +1,16 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { Heart } from 'lucide-react'; |
||||||
|
|
||||||
|
const AdminFooter = () => { |
||||||
|
return ( |
||||||
|
<footer className="sticky-footer bg-white"> |
||||||
|
<div className="container my-auto"> |
||||||
|
<div className="copyright text-center my-auto"> |
||||||
|
<span>Copyright © EcoConnect Admin 2024</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</footer> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default AdminFooter; |
@ -0,0 +1,119 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { Link } from 'react-router-dom'; |
||||||
|
import {
|
||||||
|
Search,
|
||||||
|
Bell,
|
||||||
|
Mail,
|
||||||
|
User, |
||||||
|
Settings, |
||||||
|
LogOut, |
||||||
|
Menu |
||||||
|
} from 'lucide-react'; |
||||||
|
|
||||||
|
const AdminHeader = () => { |
||||||
|
return ( |
||||||
|
<nav className="navbar navbar-expand navbar-light bg-white topbar mb-4 static-top shadow"> |
||||||
|
{/* Sidebar Toggle (Topbar) */} |
||||||
|
<button id="sidebarToggleTop" className="btn btn-link d-md-none rounded-circle mr-3"> |
||||||
|
<Menu size={20} /> |
||||||
|
</button> |
||||||
|
|
||||||
|
{/* Topbar Search */} |
||||||
|
<form className="d-none d-sm-inline-block form-inline mr-auto ml-md-3 my-2 my-md-0 mw-100 navbar-search"> |
||||||
|
<div className="input-group"> |
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control bg-light border-0 small"
|
||||||
|
placeholder="Search for..." |
||||||
|
aria-label="Search"
|
||||||
|
aria-describedby="basic-addon2"
|
||||||
|
/> |
||||||
|
<div className="input-group-append"> |
||||||
|
<button className="btn btn-primary" type="button"> |
||||||
|
<Search size={16} /> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</form> |
||||||
|
|
||||||
|
{/* Topbar Navbar */} |
||||||
|
<ul className="navbar-nav ml-auto"> |
||||||
|
{/* Nav Item - Search Dropdown (Visible Only XS) */} |
||||||
|
<li className="nav-item dropdown no-arrow d-sm-none"> |
||||||
|
<a className="nav-link dropdown-toggle" href="#" id="searchDropdown" role="button" |
||||||
|
data-bs-toggle="dropdown" aria-expanded="false"> |
||||||
|
<Search size={16} /> |
||||||
|
</a> |
||||||
|
<div className="dropdown-menu dropdown-menu-right p-3 shadow animated--grow-in" |
||||||
|
aria-labelledby="searchDropdown"> |
||||||
|
<form className="form-inline mr-auto w-100 navbar-search"> |
||||||
|
<div className="input-group"> |
||||||
|
<input type="text" className="form-control bg-light border-0 small" |
||||||
|
placeholder="Search for..." aria-label="Search" aria-describedby="basic-addon2" /> |
||||||
|
<div className="input-group-append"> |
||||||
|
<button className="btn btn-primary" type="button"> |
||||||
|
<Search size={16} /> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</form> |
||||||
|
</div> |
||||||
|
</li> |
||||||
|
|
||||||
|
{/* Nav Item - Alerts */} |
||||||
|
<li className="nav-item dropdown no-arrow mx-1"> |
||||||
|
<a className="nav-link dropdown-toggle" href="#" id="alertsDropdown" role="button" |
||||||
|
data-bs-toggle="dropdown" aria-expanded="false"> |
||||||
|
<Bell size={16} /> |
||||||
|
<span className="badge badge-danger badge-counter">3+</span> |
||||||
|
</a> |
||||||
|
<div className="dropdown-list dropdown-menu dropdown-menu-right shadow animated--grow-in" |
||||||
|
aria-labelledby="alertsDropdown"> |
||||||
|
<h6 className="dropdown-header">Alerts Center</h6> |
||||||
|
<Link className="dropdown-item d-flex align-items-center" to="#"> |
||||||
|
<div className="mr-3"> |
||||||
|
<div className="icon-circle bg-primary"> |
||||||
|
<Mail className="text-white" size={16} /> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div> |
||||||
|
<div className="small text-gray-500">March 12, 2024</div> |
||||||
|
<span className="font-weight-bold">New volunteer applications need review</span> |
||||||
|
</div> |
||||||
|
</Link> |
||||||
|
<Link className="dropdown-item text-center small text-gray-500" to="#">Show All Alerts</Link> |
||||||
|
</div> |
||||||
|
</li> |
||||||
|
|
||||||
|
<div className="topbar-divider d-none d-sm-block"></div> |
||||||
|
|
||||||
|
{/* Nav Item - User Information */} |
||||||
|
<li className="nav-item dropdown no-arrow"> |
||||||
|
<a className="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" |
||||||
|
data-bs-toggle="dropdown" aria-expanded="false"> |
||||||
|
<span className="mr-2 d-none d-lg-inline text-gray-600 small">Admin User</span> |
||||||
|
<User size={16} /> |
||||||
|
</a> |
||||||
|
<div className="dropdown-menu dropdown-menu-right shadow animated--grow-in" |
||||||
|
aria-labelledby="userDropdown"> |
||||||
|
<Link className="dropdown-item" to="#"> |
||||||
|
<User className="mr-2 text-gray-400" size={16} /> |
||||||
|
Profile |
||||||
|
</Link> |
||||||
|
<Link className="dropdown-item" to="#"> |
||||||
|
<Settings className="mr-2 text-gray-400" size={16} /> |
||||||
|
Settings |
||||||
|
</Link> |
||||||
|
<div className="dropdown-divider"></div> |
||||||
|
<Link className="dropdown-item" to="#" data-bs-toggle="modal" data-bs-target="#logoutModal"> |
||||||
|
<LogOut className="mr-2 text-gray-400" size={16} /> |
||||||
|
Logout |
||||||
|
</Link> |
||||||
|
</div> |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
</nav> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default AdminHeader; |
@ -0,0 +1,91 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { Link, useLocation } from 'react-router-dom'; |
||||||
|
import {
|
||||||
|
LayoutDashboard,
|
||||||
|
Users,
|
||||||
|
Calendar,
|
||||||
|
FileText,
|
||||||
|
Settings, |
||||||
|
Leaf |
||||||
|
} from 'lucide-react'; |
||||||
|
import AdminHeader from './AdminHeader'; |
||||||
|
import AdminFooter from './AdminFooter'; |
||||||
|
|
||||||
|
interface AdminLayoutProps { |
||||||
|
children: React.ReactNode; |
||||||
|
} |
||||||
|
|
||||||
|
const AdminLayout = ({ children }: AdminLayoutProps) => { |
||||||
|
const location = useLocation(); |
||||||
|
|
||||||
|
const isActive = (path: string) => location.pathname === path; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div id="wrapper"> |
||||||
|
{/* Sidebar */} |
||||||
|
<ul className="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion" id="accordionSidebar"> |
||||||
|
<Link className="sidebar-brand d-flex align-items-center justify-content-center" to="/admin"> |
||||||
|
<div className="sidebar-brand-icon"> |
||||||
|
<Leaf size={24} /> |
||||||
|
</div> |
||||||
|
<div className="sidebar-brand-text mx-3">EcoConnect</div> |
||||||
|
</Link> |
||||||
|
|
||||||
|
<hr className="sidebar-divider my-0" /> |
||||||
|
|
||||||
|
<li className="nav-item"> |
||||||
|
<Link className={`nav-link ${isActive('/admin') ? 'active' : ''}`} to="/admin"> |
||||||
|
<LayoutDashboard size={18} /> |
||||||
|
<span className="ml-2">Dashboard</span> |
||||||
|
</Link> |
||||||
|
</li> |
||||||
|
|
||||||
|
<li className="nav-item"> |
||||||
|
<Link className={`nav-link ${isActive('/admin/volunteers') ? 'active' : ''}`} to="/admin/volunteers"> |
||||||
|
<Users size={18} /> |
||||||
|
<span className="ml-2">Volunteers</span> |
||||||
|
</Link> |
||||||
|
</li> |
||||||
|
|
||||||
|
<li className="nav-item"> |
||||||
|
<Link className={`nav-link ${isActive('/admin/events') ? 'active' : ''}`} to="/admin/events"> |
||||||
|
<Calendar size={18} /> |
||||||
|
<span className="ml-2">Events</span> |
||||||
|
</Link> |
||||||
|
</li> |
||||||
|
|
||||||
|
<li className="nav-item"> |
||||||
|
<Link className={`nav-link ${isActive('/admin/reports') ? 'active' : ''}`} to="/admin/reports"> |
||||||
|
<FileText size={18} /> |
||||||
|
<span className="ml-2">Reports</span> |
||||||
|
</Link> |
||||||
|
</li> |
||||||
|
|
||||||
|
<li className="nav-item"> |
||||||
|
<Link className={`nav-link ${isActive('/admin/settings') ? 'active' : ''}`} to="/admin/settings"> |
||||||
|
<Settings size={18} /> |
||||||
|
<span className="ml-2">Settings</span> |
||||||
|
</Link> |
||||||
|
</li> |
||||||
|
|
||||||
|
<hr className="sidebar-divider d-none d-md-block" /> |
||||||
|
|
||||||
|
<div className="text-center d-none d-md-inline"> |
||||||
|
<button className="rounded-circle border-0" id="sidebarToggle"></button> |
||||||
|
</div> |
||||||
|
</ul> |
||||||
|
|
||||||
|
{/* Content Wrapper */} |
||||||
|
<div id="content-wrapper" className="d-flex flex-column"> |
||||||
|
{/* Main Content */} |
||||||
|
<div id="content"> |
||||||
|
<AdminHeader /> |
||||||
|
{children} |
||||||
|
</div> |
||||||
|
<AdminFooter /> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default AdminLayout; |
@ -0,0 +1,23 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { Navigate } from 'react-router-dom'; |
||||||
|
import { useAuth } from '../../context/AuthContext'; |
||||||
|
|
||||||
|
interface AdminRouteProps { |
||||||
|
children: React.ReactNode; |
||||||
|
} |
||||||
|
|
||||||
|
const AdminRoute = ({ children }: AdminRouteProps) => { |
||||||
|
const { user, loading } = useAuth(); |
||||||
|
|
||||||
|
if (loading) { |
||||||
|
return <div>Loading...</div>; |
||||||
|
} |
||||||
|
|
||||||
|
if (!user || user.role !== 'admin') { |
||||||
|
return <Navigate to="/login" replace />; |
||||||
|
} |
||||||
|
|
||||||
|
return <>{children}</>; |
||||||
|
}; |
||||||
|
|
||||||
|
export default AdminRoute; |
@ -0,0 +1,201 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import {
|
||||||
|
Users,
|
||||||
|
Calendar,
|
||||||
|
TreePine,
|
||||||
|
ArrowUp, |
||||||
|
UserPlus, |
||||||
|
Leaf, |
||||||
|
Globe, |
||||||
|
Trophy |
||||||
|
} from 'lucide-react'; |
||||||
|
import AdminLayout from './AdminLayout'; |
||||||
|
|
||||||
|
const Dashboard = () => { |
||||||
|
return ( |
||||||
|
<AdminLayout> |
||||||
|
<div className="container-fluid"> |
||||||
|
{/* Stats Row */} |
||||||
|
<div className="row g-4 mb-4"> |
||||||
|
<div className="col-md-3"> |
||||||
|
<div className="stat-card"> |
||||||
|
<div className="d-flex justify-content-between align-items-center mb-3"> |
||||||
|
<div className="icon purple"> |
||||||
|
<Users size={24} /> |
||||||
|
</div> |
||||||
|
<div className="badge bg-success">+12% <ArrowUp size={14} /></div> |
||||||
|
</div> |
||||||
|
<h3 className="h2 mb-1">1,482</h3> |
||||||
|
<p className="text-muted mb-0">Total Volunteers</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="col-md-3"> |
||||||
|
<div className="stat-card"> |
||||||
|
<div className="d-flex justify-content-between align-items-center mb-3"> |
||||||
|
<div className="icon blue"> |
||||||
|
<Calendar size={24} /> |
||||||
|
</div> |
||||||
|
<div className="badge bg-success">+5% <ArrowUp size={14} /></div> |
||||||
|
</div> |
||||||
|
<h3 className="h2 mb-1">24</h3> |
||||||
|
<p className="text-muted mb-0">Active Events</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="col-md-3"> |
||||||
|
<div className="stat-card"> |
||||||
|
<div className="d-flex justify-content-between align-items-center mb-3"> |
||||||
|
<div className="icon green"> |
||||||
|
<TreePine size={24} /> |
||||||
|
</div> |
||||||
|
<div className="badge bg-success">+18% <ArrowUp size={14} /></div> |
||||||
|
</div> |
||||||
|
<h3 className="h2 mb-1">12</h3> |
||||||
|
<p className="text-muted mb-0">Active Projects</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="col-md-3"> |
||||||
|
<div className="stat-card"> |
||||||
|
<div className="d-flex justify-content-between align-items-center mb-3"> |
||||||
|
<div className="icon orange"> |
||||||
|
<Globe size={24} /> |
||||||
|
</div> |
||||||
|
<div className="badge bg-success">+8% <ArrowUp size={14} /></div> |
||||||
|
</div> |
||||||
|
<h3 className="h2 mb-1">86%</h3> |
||||||
|
<p className="text-muted mb-0">Goal Progress</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
{/* Recent Activity */} |
||||||
|
<div className="row mb-4"> |
||||||
|
<div className="col-md-8"> |
||||||
|
<div className="activity-list"> |
||||||
|
<h4 className="mb-4">Recent Activity</h4> |
||||||
|
|
||||||
|
<div className="activity-item"> |
||||||
|
<div className="activity-icon bg-light"> |
||||||
|
<UserPlus size={20} className="text-success" /> |
||||||
|
</div> |
||||||
|
<div className="flex-grow-1"> |
||||||
|
<h6 className="mb-1">New Volunteer Registration</h6> |
||||||
|
<p className="text-muted mb-0">Sarah Johnson joined as a volunteer</p> |
||||||
|
<small className="text-muted">2 hours ago</small> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="activity-item"> |
||||||
|
<div className="activity-icon bg-light"> |
||||||
|
<Leaf size={20} className="text-success" /> |
||||||
|
</div> |
||||||
|
<div className="flex-grow-1"> |
||||||
|
<h6 className="mb-1">Project Milestone Achieved</h6> |
||||||
|
<p className="text-muted mb-0">Urban Forest Initiative reached 75% completion</p> |
||||||
|
<small className="text-muted">5 hours ago</small> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="activity-item"> |
||||||
|
<div className="activity-icon bg-light"> |
||||||
|
<Trophy size={20} className="text-success" /> |
||||||
|
</div> |
||||||
|
<div className="flex-grow-1"> |
||||||
|
<h6 className="mb-1">Achievement Unlocked</h6> |
||||||
|
<p className="text-muted mb-0">Community reached 1000 trees planted milestone</p> |
||||||
|
<small className="text-muted">1 day ago</small> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="col-md-4"> |
||||||
|
<div className="activity-list"> |
||||||
|
<h4 className="mb-4">Upcoming Events</h4> |
||||||
|
|
||||||
|
<div className="activity-item"> |
||||||
|
<div className="activity-icon bg-light"> |
||||||
|
<Calendar size={20} className="text-success" /> |
||||||
|
</div> |
||||||
|
<div className="flex-grow-1"> |
||||||
|
<h6 className="mb-1">Beach Cleanup Drive</h6> |
||||||
|
<p className="text-muted mb-0">March 15, 2024</p> |
||||||
|
<span className="status-badge active">Active</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="activity-item"> |
||||||
|
<div className="activity-icon bg-light"> |
||||||
|
<Calendar size={20} className="text-success" /> |
||||||
|
</div> |
||||||
|
<div className="flex-grow-1"> |
||||||
|
<h6 className="mb-1">Tree Planting Day</h6> |
||||||
|
<p className="text-muted mb-0">March 20, 2024</p> |
||||||
|
<span className="status-badge pending">Pending</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
{/* Projects Table */} |
||||||
|
<div className="row"> |
||||||
|
<div className="col-12"> |
||||||
|
<div className="admin-table"> |
||||||
|
<table className="table table-hover mb-0"> |
||||||
|
<thead> |
||||||
|
<tr> |
||||||
|
<th>Project Name</th> |
||||||
|
<th>Status</th> |
||||||
|
<th>Progress</th> |
||||||
|
<th>Team Lead</th> |
||||||
|
<th>Deadline</th> |
||||||
|
</tr> |
||||||
|
</thead> |
||||||
|
<tbody> |
||||||
|
<tr> |
||||||
|
<td>Urban Forest Initiative</td> |
||||||
|
<td><span className="status-badge active">Active</span></td> |
||||||
|
<td> |
||||||
|
<div className="progress" style={{ height: '6px' }}> |
||||||
|
<div className="progress-bar bg-success" style={{ width: '75%' }}></div> |
||||||
|
</div> |
||||||
|
</td> |
||||||
|
<td>Michael Chen</td> |
||||||
|
<td>Apr 15, 2024</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td>Community Garden Project</td> |
||||||
|
<td><span className="status-badge pending">Pending</span></td> |
||||||
|
<td> |
||||||
|
<div className="progress" style={{ height: '6px' }}> |
||||||
|
<div className="progress-bar bg-success" style={{ width: '45%' }}></div> |
||||||
|
</div> |
||||||
|
</td> |
||||||
|
<td>Emma Wilson</td> |
||||||
|
<td>May 1, 2024</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td>Recycling Awareness Campaign</td> |
||||||
|
<td><span className="status-badge completed">Completed</span></td> |
||||||
|
<td> |
||||||
|
<div className="progress" style={{ height: '6px' }}> |
||||||
|
<div className="progress-bar bg-success" style={{ width: '100%' }}></div> |
||||||
|
</div> |
||||||
|
</td> |
||||||
|
<td>David Park</td> |
||||||
|
<td>Mar 1, 2024</td> |
||||||
|
</tr> |
||||||
|
</tbody> |
||||||
|
</table> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</AdminLayout> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default Dashboard; |
@ -0,0 +1,134 @@ |
|||||||
|
import React, { useState, useEffect } from 'react'; |
||||||
|
import axios from 'axios'; |
||||||
|
import { Search, Trash2 } from 'lucide-react'; |
||||||
|
import '../../styles/admin.css'; |
||||||
|
|
||||||
|
interface Volunteer { |
||||||
|
_id: string; |
||||||
|
fullName: string; |
||||||
|
email: string; |
||||||
|
phone: string; |
||||||
|
organization: string; |
||||||
|
interests: string[]; |
||||||
|
availability: string[]; |
||||||
|
createdAt: string; |
||||||
|
} |
||||||
|
|
||||||
|
const VolunteerList = () => { |
||||||
|
const [volunteers, setVolunteers] = useState<Volunteer[]>([]); |
||||||
|
const [loading, setLoading] = useState(true); |
||||||
|
const [error, setError] = useState(''); |
||||||
|
const [searchTerm, setSearchTerm] = useState(''); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
fetchVolunteers(); |
||||||
|
}, []); |
||||||
|
|
||||||
|
const fetchVolunteers = async () => { |
||||||
|
try { |
||||||
|
const response = await axios.get('http://localhost:5000/volunteer/list'); |
||||||
|
setVolunteers(response.data); |
||||||
|
} catch (error) { |
||||||
|
console.error('Error fetching volunteers:', error); |
||||||
|
setError('Failed to fetch volunteers'); |
||||||
|
} finally { |
||||||
|
setLoading(false); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const handleDelete = async (id: string) => { |
||||||
|
if (window.confirm('Are you sure you want to delete this volunteer?')) { |
||||||
|
try { |
||||||
|
await axios.delete(`http://localhost:5000/volunteer/${id}`); |
||||||
|
setVolunteers(volunteers.filter(v => v._id !== id)); |
||||||
|
} catch (error) { |
||||||
|
console.error('Error deleting volunteer:', error); |
||||||
|
setError('Failed to delete volunteer'); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const filteredVolunteers = volunteers.filter(volunteer => |
||||||
|
volunteer.fullName.toLowerCase().includes(searchTerm.toLowerCase()) || |
||||||
|
volunteer.email.toLowerCase().includes(searchTerm.toLowerCase()) |
||||||
|
); |
||||||
|
|
||||||
|
if (loading) return <div className="text-center py-5">Loading...</div>; |
||||||
|
if (error) return <div className="alert alert-danger m-4">{error}</div>; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="volunteer-list py-4"> |
||||||
|
<div className="container"> |
||||||
|
<div className="d-flex justify-content-between align-items-center mb-4"> |
||||||
|
<h1>Volunteer List</h1> |
||||||
|
<div className="input-group" style={{ maxWidth: '300px' }}> |
||||||
|
<span className="input-group-text"> |
||||||
|
<Search size={18} /> |
||||||
|
</span> |
||||||
|
<input |
||||||
|
type="text" |
||||||
|
className="form-control" |
||||||
|
placeholder="Search volunteers..." |
||||||
|
value={searchTerm} |
||||||
|
onChange={(e) => setSearchTerm(e.target.value)} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="card border-0 shadow-sm"> |
||||||
|
<div className="card-body"> |
||||||
|
<div className="table-responsive"> |
||||||
|
<table className="table table-hover"> |
||||||
|
<thead> |
||||||
|
<tr> |
||||||
|
<th>Name</th> |
||||||
|
<th>Email</th> |
||||||
|
<th>Phone</th> |
||||||
|
<th>Organization</th> |
||||||
|
<th>Interests</th> |
||||||
|
<th>Availability</th> |
||||||
|
<th>Actions</th> |
||||||
|
</tr> |
||||||
|
</thead> |
||||||
|
<tbody> |
||||||
|
{filteredVolunteers.map((volunteer) => ( |
||||||
|
<tr key={volunteer._id}> |
||||||
|
<td>{volunteer.fullName}</td> |
||||||
|
<td>{volunteer.email}</td> |
||||||
|
<td>{volunteer.phone}</td> |
||||||
|
<td>{volunteer.organization || '-'}</td> |
||||||
|
<td> |
||||||
|
{volunteer.interests.map(interest => ( |
||||||
|
<span key={interest} className="badge bg-primary me-1"> |
||||||
|
{interest} |
||||||
|
</span> |
||||||
|
))} |
||||||
|
</td> |
||||||
|
<td> |
||||||
|
{volunteer.availability.map(time => ( |
||||||
|
<span key={time} className="badge bg-success me-1"> |
||||||
|
{time} |
||||||
|
</span> |
||||||
|
))} |
||||||
|
</td> |
||||||
|
<td> |
||||||
|
<button |
||||||
|
className="btn btn-danger btn-sm" |
||||||
|
onClick={() => handleDelete(volunteer._id)} |
||||||
|
> |
||||||
|
<Trash2 size={16} /> |
||||||
|
</button> |
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
))} |
||||||
|
</tbody> |
||||||
|
</table> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default VolunteerList; |
@ -0,0 +1,3 @@ |
|||||||
|
export const API_URL = process.env.NODE_ENV === 'production'
|
||||||
|
? 'https://your-backend-url.com' // Replace with your actual backend URL when deployed
|
||||||
|
: 'http://localhost:5000'; |
@ -0,0 +1,73 @@ |
|||||||
|
import React, { createContext, useContext, useState, useEffect } from 'react'; |
||||||
|
|
||||||
|
interface User { |
||||||
|
id: string; |
||||||
|
name: string; |
||||||
|
email: string; |
||||||
|
role: string; |
||||||
|
} |
||||||
|
|
||||||
|
interface AuthContextType { |
||||||
|
user: User | null; |
||||||
|
loading: boolean; |
||||||
|
logout: () => Promise<void>; |
||||||
|
checkUser: () => Promise<void>; |
||||||
|
} |
||||||
|
|
||||||
|
const AuthContext = createContext<AuthContextType | undefined>(undefined); |
||||||
|
|
||||||
|
export const AuthProvider = ({ children }: { children: React.ReactNode }) => { |
||||||
|
const [user, setUser] = useState<User | null>(null); |
||||||
|
const [loading, setLoading] = useState(true); |
||||||
|
|
||||||
|
const checkUser = async () => { |
||||||
|
try { |
||||||
|
const isLoggedIn = sessionStorage.getItem('isLoggedIn') === 'true'; |
||||||
|
|
||||||
|
if (isLoggedIn) { |
||||||
|
// Simulate a logged-in user
|
||||||
|
setUser({ |
||||||
|
id: '1', |
||||||
|
name: 'Demo User', |
||||||
|
email: 'demo@example.com', |
||||||
|
role: 'user' |
||||||
|
}); |
||||||
|
} else { |
||||||
|
setUser(null); |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
setUser(null); |
||||||
|
} finally { |
||||||
|
setLoading(false); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
checkUser(); |
||||||
|
}, []); |
||||||
|
|
||||||
|
const logout = async () => { |
||||||
|
try { |
||||||
|
sessionStorage.removeItem('isLoggedIn'); |
||||||
|
setUser(null); |
||||||
|
window.location.href = '/'; |
||||||
|
} catch (error) { |
||||||
|
console.error('Logout failed:', error); |
||||||
|
throw error; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<AuthContext.Provider value={{ user, loading, logout, checkUser }}> |
||||||
|
{!loading && children} |
||||||
|
</AuthContext.Provider> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export const useAuth = () => { |
||||||
|
const context = useContext(AuthContext); |
||||||
|
if (context === undefined) { |
||||||
|
throw new Error('useAuth must be used within an AuthProvider'); |
||||||
|
} |
||||||
|
return context; |
||||||
|
}; |
@ -0,0 +1,9 @@ |
|||||||
|
/* Global styles */ |
||||||
|
body { |
||||||
|
margin: 0; |
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', |
||||||
|
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', |
||||||
|
sans-serif; |
||||||
|
-webkit-font-smoothing: antialiased; |
||||||
|
-moz-osx-font-smoothing: grayscale; |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
import { StrictMode } from 'react'; |
||||||
|
import { createRoot } from 'react-dom/client'; |
||||||
|
import 'bootstrap/dist/css/bootstrap.min.css'; |
||||||
|
import 'bootstrap/dist/js/bootstrap.bundle.min.js'; |
||||||
|
import App from './App.tsx'; |
||||||
|
import './index.css'; |
||||||
|
|
||||||
|
createRoot(document.getElementById('root')!).render( |
||||||
|
<StrictMode> |
||||||
|
<App /> |
||||||
|
</StrictMode> |
||||||
|
); |
@ -0,0 +1,283 @@ |
|||||||
|
/* Hero Section */ |
||||||
|
.hero { |
||||||
|
min-height: 100vh; |
||||||
|
background: linear-gradient(135deg, #1a4731 0%, #2f855a 100%); |
||||||
|
padding: 4rem 0; |
||||||
|
position: relative; |
||||||
|
overflow: hidden; |
||||||
|
} |
||||||
|
|
||||||
|
.hero::before { |
||||||
|
content: ''; |
||||||
|
position: absolute; |
||||||
|
top: 0; |
||||||
|
left: 0; |
||||||
|
right: 0; |
||||||
|
bottom: 0; |
||||||
|
background: url('https://images.unsplash.com/photo-1518173946687-a4c8892bbd9f') center/cover; |
||||||
|
opacity: 0.1; |
||||||
|
} |
||||||
|
|
||||||
|
.hero-content { |
||||||
|
position: relative; |
||||||
|
z-index: 1; |
||||||
|
} |
||||||
|
|
||||||
|
.hero h1 { |
||||||
|
font-size: 4rem; |
||||||
|
font-weight: 800; |
||||||
|
background: linear-gradient(to right, #fff, #48bb78); |
||||||
|
-webkit-background-clip: text; |
||||||
|
-webkit-text-fill-color: transparent; |
||||||
|
margin-bottom: 1.5rem; |
||||||
|
line-height: 1.2; |
||||||
|
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1); |
||||||
|
} |
||||||
|
|
||||||
|
.hero .lead { |
||||||
|
font-size: 1.5rem; |
||||||
|
line-height: 1.6; |
||||||
|
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1); |
||||||
|
} |
||||||
|
|
||||||
|
.hero img { |
||||||
|
transition: transform 0.5s ease; |
||||||
|
max-width: 80%; |
||||||
|
margin: 0 auto; |
||||||
|
} |
||||||
|
|
||||||
|
.hero img:hover { |
||||||
|
transform: rotate(0deg) scale(1.02) !important; |
||||||
|
} |
||||||
|
|
||||||
|
/* Features Section */ |
||||||
|
.features { |
||||||
|
padding: 6rem 0; |
||||||
|
background: linear-gradient(135deg, #f7fafc 0%, #e6fffa 100%); |
||||||
|
} |
||||||
|
|
||||||
|
.features .card { |
||||||
|
border: none; |
||||||
|
border-radius: 20px; |
||||||
|
transition: all 0.3s ease; |
||||||
|
background: white; |
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.05); |
||||||
|
overflow: hidden; |
||||||
|
} |
||||||
|
|
||||||
|
.features .card:hover { |
||||||
|
transform: translateY(-10px); |
||||||
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); |
||||||
|
} |
||||||
|
|
||||||
|
.features .card-body { |
||||||
|
padding: 2.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.features h3 { |
||||||
|
font-size: 1.5rem; |
||||||
|
font-weight: 700; |
||||||
|
color: #2d3748; |
||||||
|
margin-bottom: 1rem; |
||||||
|
} |
||||||
|
|
||||||
|
.features p { |
||||||
|
color: #4a5568; |
||||||
|
line-height: 1.6; |
||||||
|
} |
||||||
|
|
||||||
|
.features svg { |
||||||
|
padding: 12px; |
||||||
|
background: #f0fff4; |
||||||
|
border-radius: 12px; |
||||||
|
margin-bottom: 1.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.feature-image-wrapper { |
||||||
|
max-width: 800px; |
||||||
|
margin: 0 auto; |
||||||
|
overflow: hidden; |
||||||
|
} |
||||||
|
|
||||||
|
.feature-image { |
||||||
|
width: 100%; |
||||||
|
height: 400px; |
||||||
|
object-fit: cover; |
||||||
|
object-position: center; |
||||||
|
transition: transform 0.3s ease; |
||||||
|
} |
||||||
|
|
||||||
|
.feature-image:hover { |
||||||
|
transform: scale(1.02); |
||||||
|
} |
||||||
|
|
||||||
|
/* Community Section */ |
||||||
|
.community-section { |
||||||
|
padding: 6rem 0; |
||||||
|
background: white; |
||||||
|
} |
||||||
|
|
||||||
|
.community-section img { |
||||||
|
border-radius: 20px; |
||||||
|
transition: transform 0.5s ease; |
||||||
|
} |
||||||
|
|
||||||
|
.community-section img:hover { |
||||||
|
transform: scale(1.02); |
||||||
|
} |
||||||
|
|
||||||
|
.stats-card { |
||||||
|
padding: 1.5rem; |
||||||
|
border-radius: 15px; |
||||||
|
background: #f7fafc; |
||||||
|
transition: all 0.3s ease; |
||||||
|
} |
||||||
|
|
||||||
|
.stats-card:hover { |
||||||
|
transform: translateY(-5px); |
||||||
|
background: #f0fff4; |
||||||
|
} |
||||||
|
|
||||||
|
.stats-number { |
||||||
|
font-size: 2.5rem; |
||||||
|
font-weight: 800; |
||||||
|
color: #2f855a; |
||||||
|
margin-bottom: 0.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.stats-label { |
||||||
|
color: #4a5568; |
||||||
|
font-weight: 500; |
||||||
|
} |
||||||
|
|
||||||
|
/* News Section */ |
||||||
|
.news-section { |
||||||
|
background: linear-gradient(135deg, #f8fafc 0%, #f0fff4 100%); |
||||||
|
padding: 4rem 0; |
||||||
|
} |
||||||
|
|
||||||
|
.hover-card { |
||||||
|
transition: transform 0.3s ease, box-shadow 0.3s ease; |
||||||
|
} |
||||||
|
|
||||||
|
.hover-card:hover { |
||||||
|
transform: translateY(-5px); |
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1) !important; |
||||||
|
} |
||||||
|
|
||||||
|
.animate-spin { |
||||||
|
animation: spin 1s linear infinite; |
||||||
|
} |
||||||
|
|
||||||
|
@keyframes spin { |
||||||
|
from { |
||||||
|
transform: rotate(0deg); |
||||||
|
} |
||||||
|
to { |
||||||
|
transform: rotate(360deg); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.nav-end { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 0.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.btn-outline-success { |
||||||
|
border: 2px solid #48bb78; |
||||||
|
color: #48bb78; |
||||||
|
font-weight: 600; |
||||||
|
transition: all 0.3s ease; |
||||||
|
height: 38px; |
||||||
|
padding: 0 1rem; |
||||||
|
} |
||||||
|
|
||||||
|
.btn-outline-success:hover { |
||||||
|
background-color: #48bb78; |
||||||
|
color: white; |
||||||
|
transform: translateY(-2px); |
||||||
|
} |
||||||
|
|
||||||
|
/* Dropdown Styles */ |
||||||
|
.dropdown-menu { |
||||||
|
border: none; |
||||||
|
border-radius: 12px; |
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
||||||
|
padding: 0.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.dropdown-item { |
||||||
|
color: #4a5568; |
||||||
|
border-radius: 8px; |
||||||
|
padding: 0.5rem 1rem; |
||||||
|
transition: all 0.2s ease; |
||||||
|
} |
||||||
|
|
||||||
|
.dropdown-item:hover { |
||||||
|
background-color: #f0fff4; |
||||||
|
color: #48bb78; |
||||||
|
transform: translateX(4px); |
||||||
|
} |
||||||
|
|
||||||
|
.dropdown-item.active { |
||||||
|
background-color: #48bb78; |
||||||
|
color: white; |
||||||
|
} |
||||||
|
|
||||||
|
/* Responsive Design */ |
||||||
|
@media (max-width: 991.98px) { |
||||||
|
.hero { |
||||||
|
min-height: 100vh; |
||||||
|
padding: 2rem 0; |
||||||
|
} |
||||||
|
|
||||||
|
.hero h1 { |
||||||
|
font-size: 3rem; |
||||||
|
} |
||||||
|
|
||||||
|
.hero img { |
||||||
|
max-width: 90%; |
||||||
|
} |
||||||
|
|
||||||
|
.features { |
||||||
|
padding: 4rem 0; |
||||||
|
} |
||||||
|
|
||||||
|
.community-section { |
||||||
|
padding: 4rem 0; |
||||||
|
} |
||||||
|
|
||||||
|
.navbar { |
||||||
|
top: 0.5rem; |
||||||
|
width: 98%; |
||||||
|
} |
||||||
|
|
||||||
|
.nav-end { |
||||||
|
margin-top: 1rem; |
||||||
|
width: 100%; |
||||||
|
justify-content: center; |
||||||
|
} |
||||||
|
|
||||||
|
.navbar .nav-item:not(:last-child)::after { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@media (max-width: 767.98px) { |
||||||
|
.hero { |
||||||
|
min-height: 100vh; |
||||||
|
} |
||||||
|
|
||||||
|
.hero h1 { |
||||||
|
font-size: 2.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.hero img { |
||||||
|
max-width: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
.stats-card { |
||||||
|
margin-bottom: 1rem; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,207 @@ |
|||||||
|
/* Admin Layout */ |
||||||
|
.admin-layout { |
||||||
|
display: flex; |
||||||
|
min-height: 100vh; |
||||||
|
background-color: #f8fafc; |
||||||
|
} |
||||||
|
|
||||||
|
/* Sidebar */ |
||||||
|
.admin-sidebar { |
||||||
|
width: 280px; |
||||||
|
background: #fff; |
||||||
|
border-right: 1px solid #e2e8f0; |
||||||
|
padding: 1.5rem; |
||||||
|
position: fixed; |
||||||
|
height: 100vh; |
||||||
|
overflow-y: auto; |
||||||
|
} |
||||||
|
|
||||||
|
.admin-sidebar .logo { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 0.75rem; |
||||||
|
font-size: 1.5rem; |
||||||
|
font-weight: 700; |
||||||
|
color: #1a202c; |
||||||
|
margin-bottom: 2rem; |
||||||
|
} |
||||||
|
|
||||||
|
.admin-sidebar .nav-link { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 0.75rem; |
||||||
|
padding: 0.75rem 1rem; |
||||||
|
color: #4a5568; |
||||||
|
border-radius: 0.5rem; |
||||||
|
transition: all 0.2s ease; |
||||||
|
} |
||||||
|
|
||||||
|
.admin-sidebar .nav-link:hover { |
||||||
|
background: #edf2f7; |
||||||
|
color: #2d3748; |
||||||
|
transform: translateX(4px); |
||||||
|
} |
||||||
|
|
||||||
|
.admin-sidebar .nav-link.active { |
||||||
|
background: #48bb78; |
||||||
|
color: white; |
||||||
|
} |
||||||
|
|
||||||
|
/* Main Content */ |
||||||
|
.admin-main { |
||||||
|
flex: 1; |
||||||
|
margin-left: 280px; |
||||||
|
padding: 2rem; |
||||||
|
} |
||||||
|
|
||||||
|
/* Dashboard Cards */ |
||||||
|
.stat-card { |
||||||
|
background: white; |
||||||
|
border-radius: 1rem; |
||||||
|
padding: 1.5rem; |
||||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); |
||||||
|
transition: transform 0.2s ease; |
||||||
|
} |
||||||
|
|
||||||
|
.stat-card:hover { |
||||||
|
transform: translateY(-4px); |
||||||
|
} |
||||||
|
|
||||||
|
.stat-card .icon { |
||||||
|
width: 48px; |
||||||
|
height: 48px; |
||||||
|
border-radius: 12px; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
} |
||||||
|
|
||||||
|
.stat-card .icon.purple { |
||||||
|
background: #e9d5ff; |
||||||
|
color: #7e22ce; |
||||||
|
} |
||||||
|
|
||||||
|
.stat-card .icon.blue { |
||||||
|
background: #dbeafe; |
||||||
|
color: #2563eb; |
||||||
|
} |
||||||
|
|
||||||
|
.stat-card .icon.green { |
||||||
|
background: #dcfce7; |
||||||
|
color: #16a34a; |
||||||
|
} |
||||||
|
|
||||||
|
.stat-card .icon.orange { |
||||||
|
background: #ffedd5; |
||||||
|
color: #ea580c; |
||||||
|
} |
||||||
|
|
||||||
|
/* Recent Activity */ |
||||||
|
.activity-list { |
||||||
|
background: white; |
||||||
|
border-radius: 1rem; |
||||||
|
padding: 1.5rem; |
||||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); |
||||||
|
} |
||||||
|
|
||||||
|
.activity-item { |
||||||
|
display: flex; |
||||||
|
align-items: flex-start; |
||||||
|
gap: 1rem; |
||||||
|
padding: 1rem 0; |
||||||
|
border-bottom: 1px solid #e2e8f0; |
||||||
|
} |
||||||
|
|
||||||
|
.activity-item:last-child { |
||||||
|
border-bottom: none; |
||||||
|
} |
||||||
|
|
||||||
|
.activity-icon { |
||||||
|
padding: 0.5rem; |
||||||
|
border-radius: 0.5rem; |
||||||
|
background: #f7fafc; |
||||||
|
} |
||||||
|
|
||||||
|
/* Charts */ |
||||||
|
.chart-container { |
||||||
|
background: white; |
||||||
|
border-radius: 1rem; |
||||||
|
padding: 1.5rem; |
||||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); |
||||||
|
height: 300px; |
||||||
|
} |
||||||
|
|
||||||
|
/* Tables */ |
||||||
|
.admin-table { |
||||||
|
width: 100%; |
||||||
|
background: white; |
||||||
|
border-radius: 1rem; |
||||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); |
||||||
|
} |
||||||
|
|
||||||
|
.admin-table th { |
||||||
|
background: #f8fafc; |
||||||
|
padding: 1rem; |
||||||
|
font-weight: 600; |
||||||
|
color: #4a5568; |
||||||
|
text-align: left; |
||||||
|
} |
||||||
|
|
||||||
|
.admin-table td { |
||||||
|
padding: 1rem; |
||||||
|
border-bottom: 1px solid #e2e8f0; |
||||||
|
} |
||||||
|
|
||||||
|
.admin-table tr:last-child td { |
||||||
|
border-bottom: none; |
||||||
|
} |
||||||
|
|
||||||
|
/* Status Badges */ |
||||||
|
.status-badge { |
||||||
|
padding: 0.25rem 0.75rem; |
||||||
|
border-radius: 9999px; |
||||||
|
font-size: 0.875rem; |
||||||
|
font-weight: 500; |
||||||
|
} |
||||||
|
|
||||||
|
.status-badge.active { |
||||||
|
background: #dcfce7; |
||||||
|
color: #16a34a; |
||||||
|
} |
||||||
|
|
||||||
|
.status-badge.pending { |
||||||
|
background: #fff7ed; |
||||||
|
color: #ea580c; |
||||||
|
} |
||||||
|
|
||||||
|
.status-badge.completed { |
||||||
|
background: #dbeafe; |
||||||
|
color: #2563eb; |
||||||
|
} |
||||||
|
|
||||||
|
/* Responsive Design */ |
||||||
|
@media (max-width: 1024px) { |
||||||
|
.admin-sidebar { |
||||||
|
width: 80px; |
||||||
|
padding: 1rem; |
||||||
|
} |
||||||
|
|
||||||
|
.admin-sidebar .logo span, |
||||||
|
.admin-sidebar .nav-link span { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
|
||||||
|
.admin-main { |
||||||
|
margin-left: 80px; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@media (max-width: 768px) { |
||||||
|
.admin-sidebar { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
|
||||||
|
.admin-main { |
||||||
|
margin-left: 0; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,316 @@ |
|||||||
|
/* Common Variables */ |
||||||
|
:root { |
||||||
|
--primary-color: #48bb78; |
||||||
|
--primary-hover: #38a169; |
||||||
|
--error-color: #e53e3e; |
||||||
|
--text-primary: #2d3748; |
||||||
|
--text-secondary: #718096; |
||||||
|
--bg-gradient: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); |
||||||
|
--box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1); |
||||||
|
--transition: all 0.3s ease; |
||||||
|
} |
||||||
|
|
||||||
|
/* Login Page Specific Styles */ |
||||||
|
.login-page { |
||||||
|
min-height: calc(100vh - 76px); |
||||||
|
background: var(--bg-gradient); |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
.login-card { |
||||||
|
background: white; |
||||||
|
border-radius: 20px; |
||||||
|
box-shadow: var(--box-shadow); |
||||||
|
padding: 2.5rem !important; |
||||||
|
transform-origin: center; |
||||||
|
transition: var(--transition); |
||||||
|
} |
||||||
|
|
||||||
|
.login-card:hover { |
||||||
|
transform: translateY(-5px); |
||||||
|
} |
||||||
|
|
||||||
|
.login-title { |
||||||
|
color: var(--text-primary); |
||||||
|
font-size: 2.5rem; |
||||||
|
font-weight: 700; |
||||||
|
margin-bottom: 0.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.login-subtitle { |
||||||
|
color: var(--text-secondary); |
||||||
|
font-size: 1.1rem; |
||||||
|
margin-bottom: 2rem; |
||||||
|
} |
||||||
|
|
||||||
|
/* Signup Page Specific Styles */ |
||||||
|
.signup-page { |
||||||
|
min-height: calc(100vh - 76px); |
||||||
|
background: linear-gradient(135deg, #ebf4ff 0%, #e6fffa 100%); |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
.signup-card { |
||||||
|
background: white; |
||||||
|
border-radius: 20px; |
||||||
|
box-shadow: var(--box-shadow); |
||||||
|
padding: 2.5rem !important; |
||||||
|
transform-origin: center; |
||||||
|
transition: var(--transition); |
||||||
|
} |
||||||
|
|
||||||
|
.signup-card:hover { |
||||||
|
transform: translateY(-5px); |
||||||
|
} |
||||||
|
|
||||||
|
.signup-title { |
||||||
|
color: var(--text-primary); |
||||||
|
font-size: 2.5rem; |
||||||
|
font-weight: 700; |
||||||
|
margin-bottom: 0.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.signup-subtitle { |
||||||
|
color: var(--text-secondary); |
||||||
|
font-size: 1.1rem; |
||||||
|
margin-bottom: 2rem; |
||||||
|
} |
||||||
|
|
||||||
|
/* Form Styles */ |
||||||
|
.form-control { |
||||||
|
height: 3.2rem; |
||||||
|
border: 2px solid #e2e8f0; |
||||||
|
border-radius: 12px; |
||||||
|
padding: 0.75rem 1rem; |
||||||
|
font-size: 1rem; |
||||||
|
transition: var(--transition); |
||||||
|
} |
||||||
|
|
||||||
|
.form-control:focus { |
||||||
|
border-color: var(--primary-color); |
||||||
|
box-shadow: 0 0 0 3px rgba(72, 187, 120, 0.1); |
||||||
|
} |
||||||
|
|
||||||
|
.form-label { |
||||||
|
color: var(--text-primary); |
||||||
|
font-weight: 500; |
||||||
|
margin-bottom: 0.75rem; |
||||||
|
} |
||||||
|
|
||||||
|
.form-floating { |
||||||
|
position: relative; |
||||||
|
} |
||||||
|
|
||||||
|
.form-floating > .form-control, |
||||||
|
.form-floating > .form-control-plaintext { |
||||||
|
height: calc(3.5rem + 2px); |
||||||
|
line-height: 1.25; |
||||||
|
} |
||||||
|
|
||||||
|
.form-floating > label { |
||||||
|
position: absolute; |
||||||
|
top: 0; |
||||||
|
left: 0; |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
padding: 1rem 0.75rem; |
||||||
|
overflow: hidden; |
||||||
|
text-align: start; |
||||||
|
text-overflow: ellipsis; |
||||||
|
white-space: nowrap; |
||||||
|
pointer-events: none; |
||||||
|
border: 1px solid transparent; |
||||||
|
transform-origin: 0 0; |
||||||
|
transition: opacity .1s ease-in-out,transform .1s ease-in-out; |
||||||
|
} |
||||||
|
|
||||||
|
.form-floating > .form-control:focus ~ label, |
||||||
|
.form-floating > .form-control:not(:placeholder-shown) ~ label { |
||||||
|
opacity: .65; |
||||||
|
transform: scale(.85) translateY(-0.5rem) translateX(0.15rem); |
||||||
|
} |
||||||
|
|
||||||
|
.input-group-text { |
||||||
|
background-color: transparent; |
||||||
|
border-right: none; |
||||||
|
} |
||||||
|
|
||||||
|
.input-group .form-control:focus { |
||||||
|
border-color: var(--primary-color); |
||||||
|
box-shadow: none; |
||||||
|
} |
||||||
|
|
||||||
|
.form-check-input:checked { |
||||||
|
background-color: var(--primary-color); |
||||||
|
border-color: var(--primary-color); |
||||||
|
} |
||||||
|
|
||||||
|
.form-check-input:focus { |
||||||
|
border-color: var(--primary-color); |
||||||
|
box-shadow: 0 0 0 0.25rem rgba(72, 187, 120, 0.25); |
||||||
|
} |
||||||
|
|
||||||
|
/* Button Styles */ |
||||||
|
.btn-success { |
||||||
|
height: 3.2rem; |
||||||
|
background: var(--primary-color); |
||||||
|
border: none; |
||||||
|
border-radius: 12px; |
||||||
|
font-weight: 600; |
||||||
|
font-size: 1.1rem; |
||||||
|
transition: var(--transition); |
||||||
|
} |
||||||
|
|
||||||
|
.btn-success:hover:not(:disabled) { |
||||||
|
background: var(--primary-hover); |
||||||
|
transform: translateY(-2px); |
||||||
|
} |
||||||
|
|
||||||
|
.btn-success:disabled { |
||||||
|
background: #9ae6b4; |
||||||
|
cursor: not-allowed; |
||||||
|
} |
||||||
|
|
||||||
|
/* Link Styles */ |
||||||
|
.auth-link { |
||||||
|
color: var(--primary-color); |
||||||
|
text-decoration: none; |
||||||
|
font-weight: 500; |
||||||
|
transition: var(--transition); |
||||||
|
} |
||||||
|
|
||||||
|
.auth-link:hover { |
||||||
|
color: var(--primary-hover); |
||||||
|
text-decoration: underline; |
||||||
|
} |
||||||
|
|
||||||
|
/* Icon Styles */ |
||||||
|
.auth-icon { |
||||||
|
color: var(--primary-color); |
||||||
|
} |
||||||
|
|
||||||
|
/* Error Modal Styles */ |
||||||
|
.modal-backdrop { |
||||||
|
background: rgba(0, 0, 0, 0.5); |
||||||
|
} |
||||||
|
|
||||||
|
.modal-content { |
||||||
|
border: none; |
||||||
|
border-radius: 20px; |
||||||
|
box-shadow: var(--box-shadow); |
||||||
|
} |
||||||
|
|
||||||
|
.modal-header { |
||||||
|
border: none; |
||||||
|
padding: 1.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.modal-body { |
||||||
|
padding: 1.5rem; |
||||||
|
font-size: 1.1rem; |
||||||
|
} |
||||||
|
|
||||||
|
/* Step Indicator */ |
||||||
|
.step-indicator { |
||||||
|
margin-bottom: 2rem; |
||||||
|
} |
||||||
|
|
||||||
|
.step-item { |
||||||
|
text-align: center; |
||||||
|
flex: 1; |
||||||
|
} |
||||||
|
|
||||||
|
.step-circle { |
||||||
|
width: 32px; |
||||||
|
height: 32px; |
||||||
|
border-radius: 50%; |
||||||
|
background: #e2e8f0; |
||||||
|
color: #718096; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
margin: 0 auto 8px; |
||||||
|
font-weight: 600; |
||||||
|
transition: all 0.3s ease; |
||||||
|
} |
||||||
|
|
||||||
|
.step-circle.active { |
||||||
|
background: var(--primary-color); |
||||||
|
color: white; |
||||||
|
} |
||||||
|
|
||||||
|
.step-title { |
||||||
|
font-size: 0.875rem; |
||||||
|
color: #718096; |
||||||
|
font-weight: 500; |
||||||
|
} |
||||||
|
|
||||||
|
.step-line { |
||||||
|
flex: 1; |
||||||
|
height: 2px; |
||||||
|
background: #e2e8f0; |
||||||
|
margin: 16px 8px 0; |
||||||
|
transition: all 0.3s ease; |
||||||
|
} |
||||||
|
|
||||||
|
.step-line.active { |
||||||
|
background: var(--primary-color); |
||||||
|
} |
||||||
|
|
||||||
|
/* Interest Grid */ |
||||||
|
.interest-grid { |
||||||
|
display: grid; |
||||||
|
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); |
||||||
|
gap: 1rem; |
||||||
|
margin-top: 0.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.modal-footer { |
||||||
|
border: none; |
||||||
|
padding: 1.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.btn-secondary { |
||||||
|
background: #718096; |
||||||
|
border: none; |
||||||
|
border-radius: 12px; |
||||||
|
padding: 0.75rem 1.5rem; |
||||||
|
font-weight: 500; |
||||||
|
transition: var(--transition); |
||||||
|
} |
||||||
|
|
||||||
|
.btn-secondary:hover { |
||||||
|
background: #4a5568; |
||||||
|
transform: translateY(-2px); |
||||||
|
} |
||||||
|
|
||||||
|
/* Responsive Styles */ |
||||||
|
@media (max-width: 768px) { |
||||||
|
.login-card, |
||||||
|
.signup-card { |
||||||
|
padding: 2rem !important; |
||||||
|
} |
||||||
|
|
||||||
|
.login-title, |
||||||
|
.signup-title { |
||||||
|
font-size: 2rem; |
||||||
|
} |
||||||
|
|
||||||
|
.form-control { |
||||||
|
height: 3rem; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@media (max-width: 480px) { |
||||||
|
.login-card, |
||||||
|
.signup-card { |
||||||
|
padding: 1.5rem !important; |
||||||
|
} |
||||||
|
|
||||||
|
.login-title, |
||||||
|
.signup-title { |
||||||
|
font-size: 1.75rem; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,184 @@ |
|||||||
|
/* Volunteer Registration Styles */ |
||||||
|
.volunteer-container { |
||||||
|
min-height: 100vh; |
||||||
|
background: linear-gradient(135deg, #f6f9fc 0%, #eef2ff 100%); |
||||||
|
padding: 3rem 1rem; |
||||||
|
} |
||||||
|
|
||||||
|
.volunteer-card { |
||||||
|
max-width: 800px; |
||||||
|
margin: 0 auto; |
||||||
|
background: white; |
||||||
|
border-radius: 20px; |
||||||
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1); |
||||||
|
padding: 3rem; |
||||||
|
transform: translateY(0); |
||||||
|
transition: transform 0.3s ease, box-shadow 0.3s ease; |
||||||
|
} |
||||||
|
|
||||||
|
.volunteer-card:hover { |
||||||
|
transform: translateY(-5px); |
||||||
|
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.15); |
||||||
|
} |
||||||
|
|
||||||
|
.volunteer-header { |
||||||
|
text-align: center; |
||||||
|
margin-bottom: 3rem; |
||||||
|
} |
||||||
|
|
||||||
|
.volunteer-icon { |
||||||
|
color: #48bb78; |
||||||
|
margin-bottom: 1rem; |
||||||
|
} |
||||||
|
|
||||||
|
.volunteer-header h1 { |
||||||
|
font-size: 2.5rem; |
||||||
|
color: #2d3748; |
||||||
|
margin-bottom: 0.5rem; |
||||||
|
font-weight: 700; |
||||||
|
} |
||||||
|
|
||||||
|
.volunteer-header p { |
||||||
|
color: #718096; |
||||||
|
font-size: 1.1rem; |
||||||
|
} |
||||||
|
|
||||||
|
.volunteer-form { |
||||||
|
display: grid; |
||||||
|
gap: 1.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.form-group { |
||||||
|
position: relative; |
||||||
|
} |
||||||
|
|
||||||
|
.form-group label { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
color: #4a5568; |
||||||
|
font-weight: 500; |
||||||
|
margin-bottom: 0.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.icon { |
||||||
|
color: #48bb78; |
||||||
|
margin-right: 0.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.form-control { |
||||||
|
width: 100%; |
||||||
|
padding: 0.75rem 1rem; |
||||||
|
border: 2px solid #e2e8f0; |
||||||
|
border-radius: 12px; |
||||||
|
font-size: 1rem; |
||||||
|
transition: all 0.3s ease; |
||||||
|
background: #f8fafc; |
||||||
|
} |
||||||
|
|
||||||
|
.form-control:focus { |
||||||
|
border-color: #48bb78; |
||||||
|
box-shadow: 0 0 0 3px rgba(72, 187, 120, 0.1); |
||||||
|
outline: none; |
||||||
|
background: white; |
||||||
|
} |
||||||
|
|
||||||
|
textarea.form-control { |
||||||
|
resize: vertical; |
||||||
|
min-height: 100px; |
||||||
|
} |
||||||
|
|
||||||
|
.checkbox-group { |
||||||
|
display: grid; |
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); |
||||||
|
gap: 1rem; |
||||||
|
margin-top: 0.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.checkbox-label { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
cursor: pointer; |
||||||
|
user-select: none; |
||||||
|
color: #4a5568; |
||||||
|
} |
||||||
|
|
||||||
|
.checkbox-label input[type="checkbox"] { |
||||||
|
width: 18px; |
||||||
|
height: 18px; |
||||||
|
margin-right: 0.5rem; |
||||||
|
border: 2px solid #48bb78; |
||||||
|
border-radius: 4px; |
||||||
|
cursor: pointer; |
||||||
|
accent-color: #48bb78; |
||||||
|
} |
||||||
|
|
||||||
|
.submit-button { |
||||||
|
background: #48bb78; |
||||||
|
color: white; |
||||||
|
border: none; |
||||||
|
border-radius: 12px; |
||||||
|
padding: 1rem 2rem; |
||||||
|
font-size: 1.1rem; |
||||||
|
font-weight: 600; |
||||||
|
width: 100%; |
||||||
|
cursor: pointer; |
||||||
|
transition: all 0.3s ease; |
||||||
|
margin-top: 1rem; |
||||||
|
} |
||||||
|
|
||||||
|
.submit-button:hover:not(:disabled) { |
||||||
|
background: #38a169; |
||||||
|
transform: translateY(-2px); |
||||||
|
} |
||||||
|
|
||||||
|
.submit-button:disabled { |
||||||
|
background: #9ae6b4; |
||||||
|
cursor: not-allowed; |
||||||
|
} |
||||||
|
|
||||||
|
.alert { |
||||||
|
padding: 1rem; |
||||||
|
border-radius: 12px; |
||||||
|
margin-bottom: 2rem; |
||||||
|
} |
||||||
|
|
||||||
|
.alert-success { |
||||||
|
background: #c6f6d5; |
||||||
|
color: #2f855a; |
||||||
|
border: 1px solid #9ae6b4; |
||||||
|
} |
||||||
|
|
||||||
|
.alert-danger { |
||||||
|
background: #fed7d7; |
||||||
|
color: #c53030; |
||||||
|
border: 1px solid #feb2b2; |
||||||
|
} |
||||||
|
|
||||||
|
/* Responsive Design */ |
||||||
|
@media (max-width: 768px) { |
||||||
|
.volunteer-card { |
||||||
|
padding: 2rem; |
||||||
|
} |
||||||
|
|
||||||
|
.volunteer-header h1 { |
||||||
|
font-size: 2rem; |
||||||
|
} |
||||||
|
|
||||||
|
.checkbox-group { |
||||||
|
grid-template-columns: 1fr; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@media (max-width: 480px) { |
||||||
|
.volunteer-card { |
||||||
|
padding: 1.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.volunteer-header h1 { |
||||||
|
font-size: 1.75rem; |
||||||
|
} |
||||||
|
|
||||||
|
.form-control { |
||||||
|
padding: 0.5rem 0.75rem; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
/// <reference types="vite/client" />
|
@ -0,0 +1,8 @@ |
|||||||
|
/** @type {import('tailwindcss').Config} */ |
||||||
|
export default { |
||||||
|
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], |
||||||
|
theme: { |
||||||
|
extend: {}, |
||||||
|
}, |
||||||
|
plugins: [], |
||||||
|
}; |
@ -0,0 +1,24 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"target": "ES2020", |
||||||
|
"useDefineForClassFields": true, |
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"], |
||||||
|
"module": "ESNext", |
||||||
|
"skipLibCheck": true, |
||||||
|
|
||||||
|
/* Bundler mode */ |
||||||
|
"moduleResolution": "bundler", |
||||||
|
"allowImportingTsExtensions": true, |
||||||
|
"isolatedModules": true, |
||||||
|
"moduleDetection": "force", |
||||||
|
"noEmit": true, |
||||||
|
"jsx": "react-jsx", |
||||||
|
|
||||||
|
/* Linting */ |
||||||
|
"strict": true, |
||||||
|
"noUnusedLocals": true, |
||||||
|
"noUnusedParameters": true, |
||||||
|
"noFallthroughCasesInSwitch": true |
||||||
|
}, |
||||||
|
"include": ["src"] |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
{ |
||||||
|
"files": [], |
||||||
|
"references": [ |
||||||
|
{ "path": "./tsconfig.app.json" }, |
||||||
|
{ "path": "./tsconfig.node.json" } |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"target": "ES2022", |
||||||
|
"lib": ["ES2023"], |
||||||
|
"module": "ESNext", |
||||||
|
"skipLibCheck": true, |
||||||
|
|
||||||
|
/* Bundler mode */ |
||||||
|
"moduleResolution": "bundler", |
||||||
|
"allowImportingTsExtensions": true, |
||||||
|
"isolatedModules": true, |
||||||
|
"moduleDetection": "force", |
||||||
|
"noEmit": true, |
||||||
|
|
||||||
|
/* Linting */ |
||||||
|
"strict": true, |
||||||
|
"noUnusedLocals": true, |
||||||
|
"noUnusedParameters": true, |
||||||
|
"noFallthroughCasesInSwitch": true |
||||||
|
}, |
||||||
|
"include": ["vite.config.ts"] |
||||||
|
} |
@ -0,0 +1,10 @@ |
|||||||
|
import { defineConfig } from 'vite'; |
||||||
|
import react from '@vitejs/plugin-react'; |
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({ |
||||||
|
plugins: [react()], |
||||||
|
optimizeDeps: { |
||||||
|
exclude: ['lucide-react'], |
||||||
|
}, |
||||||
|
}); |
Loading…
Reference in new issue