First commit

This commit is contained in:
Kevin Adametz 2025-10-20 17:50:35 +02:00
commit 7cf3558ba7
12933 changed files with 1180047 additions and 0 deletions

107
dev/DOMAINS-CONFIG.md Normal file
View file

@ -0,0 +1,107 @@
# Multi-Domain-Konfiguration für Laravel
Dieses Projekt unterstützt mehrere Domains mit unterschiedlichen Styles und Konfigurationen.
Die Domains werden über `.env`-Variablen konfiguriert.
## Basis-Konfiguration
Füge die folgenden Variablen zu deiner `.env`-Datei hinzu:
```env
# Domain-Konfigurationen
DOMAIN_ADMIN=portal.b2in.local
DOMAIN_ADMIN_NAME="Admin Portal"
DOMAIN_MAIN=b2in.local
DOMAIN_MAIN_NAME="B2in"
DOMAIN_LANDING1=landing1.local
DOMAIN_LANDING1_NAME="Landing 1"
DOMAIN_LANDING1_PRIMARY="#4f46e5"
DOMAIN_LANDING1_ACCENT="#f59e0b"
DOMAIN_LANDING2=landing2.local
DOMAIN_LANDING2_NAME="Landing 2"
DOMAIN_LANDING2_PRIMARY="#0d9488"
DOMAIN_LANDING2_ACCENT="#ec4899"
# Entwicklungseinstellungen für Domains
DEV_SIMULATE_DOMAIN=false
DEV_SIMULATED_DOMAIN=b2in.local
```
## Entwicklungsmodus
Während der Entwicklung kann es nützlich sein, verschiedene Domains zu simulieren,
ohne die Host-Datei bearbeiten zu müssen:
1. Setze `DEV_SIMULATE_DOMAIN=true` in deiner `.env`-Datei.
2. Setze `DEV_SIMULATED_DOMAIN` auf eine der konfigurierten Domains (z.B. `landing1.local`).
Dies bewirkt, dass die Anwendung so reagiert, als würde sie unter der angegebenen Domain laufen,
unabhängig von der tatsächlichen URL.
## Domain-spezifische Konfiguration
Jede Domain kann eigene Einstellungen haben:
### Admin-Portal (portal.b2in.local)
- `DOMAIN_ADMIN`: Die Domain für den Admin-Bereich
- `DOMAIN_ADMIN_NAME`: Der Name des Admin-Bereichs
### Haupt-Website (b2in.local)
- `DOMAIN_MAIN`: Die Domain für die Haupt-Website
- `DOMAIN_MAIN_NAME`: Der Name der Haupt-Website
### Landing-Page 1 (landing1.local)
- `DOMAIN_LANDING1`: Die Domain für Landing-Page 1
- `DOMAIN_LANDING1_NAME`: Der Name der Landing-Page 1
- `DOMAIN_LANDING1_PRIMARY`: Die primäre Farbe im HEX-Format (z.B. `#4f46e5`)
- `DOMAIN_LANDING1_ACCENT`: Die Akzentfarbe im HEX-Format (z.B. `#f59e0b`)
### Landing-Page 2 (landing2.local)
- `DOMAIN_LANDING2`: Die Domain für Landing-Page 2
- `DOMAIN_LANDING2_NAME`: Der Name der Landing-Page 2
- `DOMAIN_LANDING2_PRIMARY`: Die primäre Farbe im HEX-Format (z.B. `#0d9488`)
- `DOMAIN_LANDING2_ACCENT`: Die Akzentfarbe im HEX-Format (z.B. `#ec4899`)
## Hosts-Datei konfigurieren
Um die verschiedenen Domains lokal zu testen, füge folgende Zeilen zu deiner Hosts-Datei hinzu:
```
127.0.0.1 portal.b2in.local
127.0.0.1 b2in.local
127.0.0.1 landing1.local
127.0.0.1 landing2.local
```
Die Hosts-Datei befindet sich unter:
- Windows: `C:\Windows\System32\drivers\etc\hosts`
- macOS/Linux: `/etc/hosts`
## Verwendung im Code
Im Code kannst du auf die Domain-Konfiguration zugreifen:
```php
// Domain-Name in einem View
{{ $domainName }}
// Theme in einem View
{{ $theme }}
// Vollständige Domain-Konfiguration
{{ $domainConfig['description'] }}
// Über die app-Config
{{ config('app.theme') }}
{{ config('app.domain_name') }}
```
Jede Domain lädt automatisch ihr eigenes CSS und andere domainspezifische Assets.

View file

@ -0,0 +1,185 @@
# Laravel Fortify & Sanctum Setup
## Übersicht
Dieses Projekt wurde mit Laravel Fortify für die Authentifizierung und Laravel Sanctum für API-Token-Management konfiguriert. Die Authentifizierung verwendet Livewire-Komponenten mit Flux UI für eine moderne, reaktive Benutzeroberfläche.
## Installation
Die Pakete wurden bereits installiert und konfiguriert:
```bash
composer require laravel/fortify laravel/sanctum
```
## Konfiguration
### Fortify
- **Konfigurationsdatei**: `config/fortify.php`
- **Service Provider**: `app/Providers/FortifyServiceProvider.php`
- **Aktionen**: `app/Actions/Fortify/`
- **Livewire-Komponenten**: `resources/views/livewire/auth/`
### Sanctum
- **Konfigurationsdatei**: `config/sanctum.php`
- **Migrationen**: Ausgeführt
- **User Model**: Aktualisiert mit `HasApiTokens` Trait
## Features
### Fortify Features (aktiviert)
- ✅ Benutzerregistrierung (Livewire + Flux UI)
- ✅ Passwort-Reset (Livewire + Flux UI)
- ✅ Profilaktualisierung
- ✅ Passwort-Update
- ✅ Zwei-Faktor-Authentifizierung
- ⚠️ E-Mail-Verifizierung (deaktiviert in der Konfiguration)
### Sanctum Features
- ✅ API-Token-Erstellung
- ✅ Token-Revocation
- ✅ Geschützte API-Routen
## Technologie-Stack
- **Laravel Fortify**: Backend-Authentifizierung
- **Laravel Sanctum**: API-Token-Management
- **Livewire**: Reaktive Frontend-Komponenten
- **Flux UI**: Moderne UI-Komponenten
- **Volt**: Livewire-Komponenten-Syntax
## Routen
### Web-Authentifizierung (portal.b2in.test)
- `GET /login` - Anmeldeseite (Livewire)
- `POST /login` - Anmeldung (Livewire)
- `POST /logout` - Abmeldung
- `GET /register` - Registrierungsseite (Livewire)
- `POST /register` - Registrierung (Livewire)
- `GET /forgot-password` - Passwort vergessen (Livewire)
- `POST /forgot-password` - Passwort-Reset-Link senden (Livewire)
- `GET /reset-password/{token}` - Passwort zurücksetzen (Livewire)
- `POST /reset-password` - Neues Passwort setzen (Livewire)
- `GET /verify-email` - E-Mail-Verifizierung (Livewire)
- `GET /confirm-password` - Passwort bestätigen (Livewire)
### API-Routen (api.b2in.test)
- `GET /api/user` - Aktueller Benutzer (geschützt)
- `GET /api/profile` - Benutzerprofil (geschützt)
- `POST /api/login` - API-Anmeldung (öffentlich)
## Verwendung
### Web-Authentifizierung
1. Besuchen Sie `http://portal.b2in.test/login`
2. Registrieren Sie sich oder melden Sie sich an
3. Nutzen Sie die verschiedenen Authentifizierungsfeatures
### API-Authentifizierung
1. **Token erstellen**:
```bash
curl -X POST http://api.b2in.test/login \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"password"}'
```
2. **Geschützte Route aufrufen**:
```bash
curl -X GET http://api.b2in.test/api/user \
-H "Authorization: Bearer YOUR_TOKEN_HERE"
```
### Zwei-Faktor-Authentifizierung
1. Aktivieren Sie 2FA in Ihren Benutzereinstellungen
2. Scannen Sie den QR-Code mit Ihrer Authentifizierungs-App
3. Geben Sie den Code bei der Anmeldung ein
## Livewire-Komponenten
Alle Auth-Komponenten befinden sich in `resources/views/livewire/auth/`:
- `login.blade.php` - Anmeldeseite mit Flux UI
- `register.blade.php` - Registrierungsseite mit Flux UI
- `forgot-password.blade.php` - Passwort vergessen mit Flux UI
- `reset-password.blade.php` - Passwort zurücksetzen mit Flux UI
- `verify-email.blade.php` - E-Mail-Verifizierung mit Flux UI
- `confirm-password.blade.php` - Passwort bestätigen mit Flux UI
### Flux UI Features
- Moderne, responsive Benutzeroberfläche
- Eingabevalidierung in Echtzeit
- Rate Limiting mit visuellen Feedback
- Dark Mode Unterstützung
- Barrierefreiheit
## Anpassungen
### Fortify-Konfiguration anpassen
Bearbeiten Sie `config/fortify.php` um Features zu aktivieren/deaktivieren:
```php
'features' => [
Features::registration(),
Features::resetPasswords(),
Features::emailVerification(), // Aktivieren für E-Mail-Verifizierung
Features::updateProfileInformation(),
Features::updatePasswords(),
Features::twoFactorAuthentication([
'confirm' => true,
'confirmPassword' => true,
]),
],
```
### Livewire-Komponenten anpassen
Bearbeiten Sie die Komponenten in `resources/views/livewire/auth/`:
```php
// Beispiel: Login-Komponente anpassen
new #[Layout('components.layouts.auth')] class extends Component {
#[Validate('required|string|email')]
public string $email = '';
// Ihre Anpassungen hier...
}
```
### Sanctum-Konfiguration anpassen
Bearbeiten Sie `config/sanctum.php` um Token-Einstellungen zu ändern:
```php
'expiration' => null, // Token-Ablaufzeit (null = nie)
'guard' => ['web'], // Guards für Sanctum
```
## Sicherheit
- Alle Passwörter werden automatisch gehashed
- CSRF-Schutz ist aktiviert
- Rate Limiting ist konfiguriert
- Zwei-Faktor-Authentifizierung ist verfügbar
- API-Tokens können widerrufen werden
- Livewire-Komponenten haben eingebaute Sicherheitsfeatures
## Nächste Schritte
1. Konfigurieren Sie E-Mail-Einstellungen in `.env` für Passwort-Reset
2. Aktivieren Sie E-Mail-Verifizierung falls gewünscht
3. Passen Sie die Livewire-Komponenten an Ihr Design an
4. Erstellen Sie zusätzliche API-Routen nach Bedarf
5. Konfigurieren Sie Flux UI Themes nach Ihren Wünschen

0
dev/HERO-ICONS-USAGE.md Normal file
View file

191
dev/LOCAL-DEVELOPMENT.md Normal file
View file

@ -0,0 +1,191 @@
# Lokale Entwicklung - Domain-Setup
## 🚀 Schnellstart ohne Domain-Konfiguration
Das System unterstützt mehrere Wege, um die verschiedenen Themes zu testen:
### 1. **URL-Parameter (Empfohlen für Testing)**
```
http://localhost:8000/theme-demo?theme=b2in
http://localhost:8000/theme-demo?theme=b2a
http://localhost:8000/theme-demo?theme=stileigentum
http://localhost:8000/theme-demo?theme=style2own
```
### 2. **Pfad-basiert (Für lokale Entwicklung)**
```
http://localhost:8000/b2in/
http://localhost:8000/b2a/
http://localhost:8000/stileigentum/
http://localhost:8000/style2own/
```
### 3. **Echte Domains (Für Produktion)**
## 🔧 Domain-Setup für lokale Entwicklung
### Option A: Hosts-Datei bearbeiten
Fügen Sie folgende Zeilen zu Ihrer `hosts`-Datei hinzu:
**Windows:** `C:\Windows\System32\drivers\etc\hosts`
**macOS/Linux:** `/etc/hosts`
```
127.0.0.1 b2in.test
127.0.0.1 b2a.test
127.0.0.1 stileigentum.test
127.0.0.1 style2own.test
127.0.0.1 portal.b2in.test
```
### Option B: Laravel Valet (macOS)
```bash
valet link b2in
valet link b2a
valet link stileigentum
valet link style2own
```
### Option C: Docker mit nginx
```yaml
# docker-compose.yml
version: '3.8'
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- app
app:
build: .
volumes:
- .:/var/www/html
```
## 🌐 Verfügbare URLs
Nach dem Domain-Setup funktionieren folgende URLs:
### **B2IN Theme (Anthracite + Dynamic Blue)**
- https://b2in.test/
- https://b2in.test/about
- https://b2in.test/partner
- https://b2in.test/theme-demo
### **B2A Theme (Azur Blue + Liberty Red)**
- https://b2a.test/
- https://b2a.test/about
- https://b2a.test/partner
- https://b2a.test/theme-demo
### **Stileigentum Theme (Style Blue + Style Sun)**
- https://stileigentum.test/
- https://stileigentum.test/about
- https://stileigentum.test/partner
- https://stileigentum.test/theme-demo
### **Style2own Theme (Imperial Blue + Sand Gold)**
- https://style2own.test/
- https://style2own.test/about
- https://style2own.test/partner
- https://style2own.test/theme-demo
## 🎨 Theme-Testing
### Demo-Seite verwenden
Besuchen Sie `/theme-demo` auf jeder Domain oder verwenden Sie URL-Parameter:
```
http://localhost:8000/theme-demo?theme=b2in
```
Die Demo-Seite zeigt:
- ✅ Aktuelle Domain-Konfiguration
- ✅ Logo-Demo (positiv/negativ)
- ✅ Farb-Demo (Primary/Secondary/Accent)
- ✅ Button-Demo mit Hover-Effekten
- ✅ Theme-Switching-Links
## 🔍 Troubleshooting
### Domains funktionieren nicht
1. **Hosts-Datei prüfen:** Stellen Sie sicher, dass die Domains in der hosts-Datei stehen
2. **DNS-Cache leeren:**
- Windows: `ipconfig /flushdns`
- macOS: `sudo dscacheutil -flushcache`
- Linux: `sudo systemctl restart systemd-resolved`
### Themes werden nicht geladen
1. **Vite-Assets kompilieren:**
```bash
npm run build
```
2. **Cache leeren:**
```bash
php artisan cache:clear
php artisan config:clear
php artisan view:clear
```
### Logos werden nicht angezeigt
1. **Logo-Pfade prüfen:** Überprüfen Sie `public/img/logos/`
2. **Dateiberechtigungen:** Stellen Sie sicher, dass die Dateien lesbar sind
## 📝 Entwicklungstipps
### Theme-Switching testen
```bash
# Alle Themes schnell testen
curl -H "Host: b2in.test" http://localhost:8000/
curl -H "Host: b2a.test" http://localhost:8000/
curl -H "Host: stileigentum.test" http://localhost:8000/
curl -H "Host: style2own.test" http://localhost:8000/
```
### Assets kompilieren
```bash
# Für alle Themes
npm run build
# Für spezifisches Theme (falls konfiguriert)
npm run build:b2in
npm run build:b2a
npm run build:stileigentum
npm run build:style2own
```
### Debugging
```bash
# Theme-Konfiguration anzeigen
php artisan tinker
>>> config('app.theme')
>>> config('domains.domains.b2in')
```
## 🚀 Produktions-Deployment
Für die Produktion müssen Sie:
1. **Domain-DNS konfigurieren**
2. **SSL-Zertifikate einrichten**
3. **Vite-Assets für alle Themes kompilieren**
4. **Umgebungsvariablen setzen**
```bash
# Produktions-Build
npm run build
# Alle Theme-Assets kompilieren
npm run build:b2in
npm run build:b2a
npm run build:stileigentum
npm run build:style2own
```

24
dev/b2in-layout-v10/.gitignore vendored Normal file
View file

@ -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?

View file

@ -0,0 +1,73 @@
# Welcome to your Lovable project
## Project info
**URL**: https://lovable.dev/projects/808f6516-394f-4fef-bba0-fe03d1d6ea51
## How can I edit this code?
There are several ways of editing your application.
**Use Lovable**
Simply visit the [Lovable Project](https://lovable.dev/projects/808f6516-394f-4fef-bba0-fe03d1d6ea51) and start prompting.
Changes made via Lovable will be committed automatically to this repo.
**Use your preferred IDE**
If you want to work locally using your own IDE, you can clone this repo and push changes. Pushed changes will also be reflected in Lovable.
The only requirement is having Node.js & npm installed - [install with nvm](https://github.com/nvm-sh/nvm#installing-and-updating)
Follow these steps:
```sh
# Step 1: Clone the repository using the project's Git URL.
git clone <YOUR_GIT_URL>
# Step 2: Navigate to the project directory.
cd <YOUR_PROJECT_NAME>
# Step 3: Install the necessary dependencies.
npm i
# Step 4: Start the development server with auto-reloading and an instant preview.
npm run dev
```
**Edit a file directly in GitHub**
- Navigate to the desired file(s).
- Click the "Edit" button (pencil icon) at the top right of the file view.
- Make your changes and commit the changes.
**Use GitHub Codespaces**
- Navigate to the main page of your repository.
- Click on the "Code" button (green button) near the top right.
- Select the "Codespaces" tab.
- Click on "New codespace" to launch a new Codespace environment.
- Edit files directly within the Codespace and commit and push your changes once you're done.
## What technologies are used for this project?
This project is built with:
- Vite
- TypeScript
- React
- shadcn-ui
- Tailwind CSS
## How can I deploy this project?
Simply open [Lovable](https://lovable.dev/projects/808f6516-394f-4fef-bba0-fe03d1d6ea51) and click on Share -> Publish.
## Can I connect a custom domain to my Lovable project?
Yes, you can!
To connect a domain, navigate to Project > Settings > Domains and click Connect Domain.
Read more here: [Setting up a custom domain](https://docs.lovable.dev/features/custom-domain#custom-domain)

Binary file not shown.

View file

@ -0,0 +1,20 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/index.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}

View file

@ -0,0 +1,26 @@
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 }],
"@typescript-eslint/no-unused-vars": "off",
},
},
);

View file

@ -0,0 +1,28 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Homy - Luxury Accommodation & Hotel Booking</title>
<meta name="description" content="Discover premium accommodations and luxury hotels worldwide. Book secure, affordable stays with exceptional service and modern amenities." />
<meta name="author" content="Lovable" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=IBM+Plex+Sans:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<meta property="og:title" content="Homy - Luxury Accommodation & Hotel Booking" />
<meta property="og:description" content="Discover premium accommodations and luxury hotels worldwide. Book secure, affordable stays with exceptional service and modern amenities." />
<meta property="og:type" content="website" />
<meta property="og:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@lovable_dev" />
<meta name="twitter:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

6766
dev/b2in-layout-v10/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,83 @@
{
"name": "vite_react_shadcn_ts",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"build:dev": "vite build --mode development",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@hookform/resolvers": "^3.10.0",
"@radix-ui/react-accordion": "^1.2.11",
"@radix-ui/react-alert-dialog": "^1.1.14",
"@radix-ui/react-aspect-ratio": "^1.1.7",
"@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-checkbox": "^1.3.2",
"@radix-ui/react-collapsible": "^1.1.11",
"@radix-ui/react-context-menu": "^2.2.15",
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-dropdown-menu": "^2.1.15",
"@radix-ui/react-hover-card": "^1.1.14",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-menubar": "^1.1.15",
"@radix-ui/react-navigation-menu": "^1.2.13",
"@radix-ui/react-popover": "^1.1.14",
"@radix-ui/react-progress": "^1.1.7",
"@radix-ui/react-radio-group": "^1.3.7",
"@radix-ui/react-scroll-area": "^1.2.9",
"@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slider": "^1.3.5",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.5",
"@radix-ui/react-tabs": "^1.1.12",
"@radix-ui/react-toast": "^1.2.14",
"@radix-ui/react-toggle": "^1.1.9",
"@radix-ui/react-toggle-group": "^1.1.10",
"@radix-ui/react-tooltip": "^1.2.7",
"@tanstack/react-query": "^5.83.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"date-fns": "^3.6.0",
"embla-carousel-react": "^8.6.0",
"input-otp": "^1.4.2",
"lucide-react": "^0.462.0",
"next-themes": "^0.3.0",
"react": "^18.3.1",
"react-day-picker": "^8.10.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.61.1",
"react-resizable-panels": "^2.1.9",
"react-router-dom": "^6.30.1",
"recharts": "^2.15.4",
"sonner": "^1.7.4",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7",
"vaul": "^0.9.9",
"zod": "^3.25.76"
},
"devDependencies": {
"@eslint/js": "^9.32.0",
"@tailwindcss/typography": "^0.5.16",
"@types/node": "^22.16.5",
"@types/react": "^18.3.23",
"@types/react-dom": "^18.3.7",
"@vitejs/plugin-react-swc": "^3.11.0",
"autoprefixer": "^10.4.21",
"eslint": "^9.32.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"globals": "^15.15.0",
"lovable-tagger": "^1.1.9",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.17",
"typescript": "^5.8.3",
"typescript-eslint": "^8.38.0",
"vite": "^5.4.19"
}
}

View file

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" fill="none"><rect width="1200" height="1200" fill="#EAEAEA" rx="3"/><g opacity=".5"><g opacity=".5"><path fill="#FAFAFA" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/></g><path stroke="url(#a)" stroke-width="2.418" d="M0-1.209h553.581" transform="scale(1 -1) rotate(45 1163.11 91.165)"/><path stroke="url(#b)" stroke-width="2.418" d="M404.846 598.671h391.726"/><path stroke="url(#c)" stroke-width="2.418" d="M599.5 795.742V404.017"/><path stroke="url(#d)" stroke-width="2.418" d="m795.717 796.597-391.441-391.44"/><path fill="#fff" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/><g clip-path="url(#e)"><path fill="#666" fill-rule="evenodd" d="M616.426 586.58h-31.434v16.176l3.553-3.554.531-.531h9.068l.074-.074 8.463-8.463h2.565l7.18 7.181V586.58Zm-15.715 14.654 3.698 3.699 1.283 1.282-2.565 2.565-1.282-1.283-5.2-5.199h-6.066l-5.514 5.514-.073.073v2.876a2.418 2.418 0 0 0 2.418 2.418h26.598a2.418 2.418 0 0 0 2.418-2.418v-8.317l-8.463-8.463-7.181 7.181-.071.072Zm-19.347 5.442v4.085a6.045 6.045 0 0 0 6.046 6.045h26.598a6.044 6.044 0 0 0 6.045-6.045v-7.108l1.356-1.355-1.282-1.283-.074-.073v-17.989h-38.689v23.43l-.146.146.146.147Z" clip-rule="evenodd"/></g><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/></g><defs><linearGradient id="a" x1="554.061" x2="-.48" y1=".083" y2=".087" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="796.912" x2="404.507" y1="599.963" y2="599.965" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="c" x1="600.792" x2="600.794" y1="403.677" y2="796.082" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="404.85" x2="796.972" y1="403.903" y2="796.02" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><clipPath id="e"><path fill="#fff" d="M581.364 580.535h38.689v38.689h-38.689z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -0,0 +1,14 @@
User-agent: Googlebot
Allow: /
User-agent: Bingbot
Allow: /
User-agent: Twitterbot
Allow: /
User-agent: facebookexternalhit
Allow: /
User-agent: *
Allow: /

View file

@ -0,0 +1,42 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}

View file

@ -0,0 +1,40 @@
import { Toaster } from "@/components/ui/toaster";
import { Toaster as Sonner } from "@/components/ui/sonner";
import { TooltipProvider } from "@/components/ui/tooltip";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Index from "./pages/Index";
import Partner from "./pages/Partner";
import About from "./pages/About";
import Ecosystem from "./pages/Ecosystem";
import Magazin from "./pages/Magazin";
import MagazinDetail from "./pages/MagazinDetail";
import Contact from "./pages/Contact";
import NotFound from "./pages/NotFound";
const queryClient = new QueryClient();
const App = () => (
<QueryClientProvider client={queryClient}>
<TooltipProvider>
<Toaster />
<Sonner />
<BrowserRouter>
<Routes>
<Route path="/" element={<Index />} />
<Route path="/services" element={<Partner />} />
<Route path="/partner" element={<Partner />} />
<Route path="/about" element={<About />} />
<Route path="/ecosystem" element={<Ecosystem />} />
<Route path="/magazin" element={<Magazin />} />
<Route path="/magazin/:id" element={<MagazinDetail />} />
<Route path="/contact" element={<Contact />} />
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
</TooltipProvider>
</QueryClientProvider>
);
export default App;

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Ebene_1" data-name="Ebene 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 283.46 160.63">
<defs>
<style>
.cls-1 {
fill: #2c9fda;
}
.cls-2 {
fill: #fff;
}
</style>
</defs>
<path class="cls-1" d="M36.8,55.75C45.39,25.8,75.26,3.7,110.8,3.7s65.41,22.1,74,52.04h13.94C185.49,23.2,151.12,0,110.8,0S36.12,23.2,22.86,55.75h13.94Z"/>
<g>
<path class="cls-2" d="M0,160.63V60.03h39.61c7.33,0,13.44,1.11,18.33,3.34,4.89,2.23,8.56,5.28,10.99,9.15,2.43,3.87,3.65,8.33,3.65,13.37,0,3.96-.78,7.42-2.34,10.36-1.57,2.95-3.7,5.36-6.4,7.22-2.7,1.87-5.77,3.21-9.22,4.02v1.01c3.75.14,7.29,1.2,10.62,3.21,3.33,2,6.03,4.79,8.11,8.37,2.08,3.58,3.12,7.84,3.12,12.79,0,5.31-1.3,10.06-3.89,14.25-2.59,4.19-6.42,7.48-11.49,9.89-5.07,2.41-11.38,3.61-18.93,3.61H0ZM20.44,102.29h16.15c2.9,0,5.52-.53,7.84-1.59,2.32-1.06,4.14-2.57,5.46-4.52,1.32-1.96,1.98-4.29,1.98-6.99,0-3.65-1.29-6.62-3.85-8.91-2.57-2.3-6.24-3.44-11.02-3.44h-16.55v25.45ZM20.44,143.68h17.69c5.99,0,10.36-1.17,13.14-3.51,2.77-2.34,4.16-5.42,4.16-9.25,0-2.84-.68-5.34-2.04-7.53-1.36-2.18-3.29-3.89-5.8-5.13-2.5-1.24-5.47-1.86-8.91-1.86h-18.23v27.28Z"/>
<path class="cls-2" d="M90.07,160.63v-14.85l35.38-33.42c3.04-2.97,5.59-5.66,7.67-8.07,2.08-2.41,3.66-4.77,4.76-7.09,1.09-2.32,1.64-4.83,1.64-7.53,0-3.01-.67-5.6-2.01-7.76-1.34-2.16-3.17-3.83-5.49-5-2.32-1.17-4.98-1.75-7.97-1.75s-5.79.63-8.11,1.89c-2.32,1.26-4.11,3.06-5.36,5.4s-1.88,5.15-1.88,8.44h-19.57c0-6.53,1.48-12.21,4.46-17.05,2.97-4.84,7.09-8.57,12.36-11.21,5.27-2.63,11.35-3.95,18.23-3.95s13.22,1.27,18.5,3.81c5.27,2.54,9.37,6.04,12.3,10.5,2.93,4.46,4.39,9.59,4.39,15.39,0,3.74-.73,7.43-2.18,11.07-1.45,3.65-4.05,7.73-7.77,12.25s-9.01,9.94-15.85,16.24l-15.08,14.99v.74h42.22v16.95h-70.63Z"/>
<path class="cls-2" d="M207.81,160.63h-33.41v-12.72h10.4v-71.07h-10.4v-12.72h33.41v12.72h-10.51v71.07h10.51v12.72Z"/>
<path class="cls-2" d="M269.53,160.63l-25.22-53.23-8.52-20.6h-.33v73.84h-11.95v-96.51h14.05l25.11,53.23,8.52,20.6h.33v-73.83h11.95v96.51h-13.94Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Ebene_1" data-name="Ebene 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 283.46 160.63">
<defs>
<style>
.cls-1 {
fill: #2c9fda;
}
.cls-2 {
fill: #314052;
}
</style>
</defs>
<path class="cls-1" d="M36.8,55.75C45.39,25.8,75.26,3.7,110.8,3.7s65.41,22.1,74,52.04h13.94C185.49,23.2,151.12,0,110.8,0S36.12,23.2,22.87,55.75h13.94Z"/>
<g>
<path class="cls-2" d="M0,160.63V60.03h39.61c7.33,0,13.44,1.11,18.33,3.34,4.89,2.23,8.56,5.28,10.99,9.15,2.43,3.87,3.65,8.33,3.65,13.37,0,3.96-.78,7.42-2.34,10.36-1.57,2.95-3.7,5.36-6.4,7.22-2.7,1.87-5.77,3.21-9.22,4.02v1.01c3.75.14,7.29,1.2,10.62,3.21,3.33,2,6.03,4.79,8.11,8.37,2.08,3.58,3.12,7.84,3.12,12.79,0,5.31-1.3,10.06-3.89,14.25-2.59,4.19-6.42,7.48-11.49,9.89-5.07,2.41-11.38,3.61-18.93,3.61H0ZM20.44,102.29h16.15c2.9,0,5.52-.53,7.84-1.59,2.32-1.06,4.14-2.57,5.46-4.52,1.32-1.96,1.98-4.29,1.98-6.99,0-3.65-1.29-6.62-3.85-8.91-2.57-2.3-6.24-3.44-11.02-3.44h-16.55v25.45ZM20.44,143.68h17.69c5.99,0,10.36-1.17,13.14-3.51,2.77-2.34,4.16-5.42,4.16-9.25,0-2.84-.68-5.34-2.04-7.53-1.36-2.18-3.29-3.89-5.8-5.13-2.5-1.24-5.47-1.86-8.91-1.86h-18.23v27.28Z"/>
<path class="cls-2" d="M90.07,160.63v-14.85l35.38-33.42c3.04-2.97,5.59-5.66,7.67-8.07,2.08-2.41,3.66-4.77,4.76-7.09,1.09-2.32,1.64-4.83,1.64-7.53,0-3.01-.67-5.6-2.01-7.76s-3.17-3.83-5.49-5c-2.32-1.17-4.98-1.75-7.97-1.75s-5.79.63-8.11,1.89c-2.32,1.26-4.11,3.06-5.36,5.4s-1.88,5.15-1.88,8.44h-19.57c0-6.53,1.48-12.21,4.46-17.05,2.97-4.84,7.09-8.57,12.36-11.21,5.27-2.63,11.35-3.95,18.23-3.95s13.22,1.27,18.5,3.81c5.27,2.54,9.37,6.04,12.3,10.5,2.93,4.46,4.39,9.59,4.39,15.39,0,3.74-.73,7.43-2.18,11.07-1.45,3.65-4.05,7.73-7.77,12.25s-9.01,9.94-15.85,16.24l-15.08,14.99v.74h42.22v16.95h-70.63Z"/>
<path class="cls-2" d="M207.81,160.63h-33.41v-12.72h10.4v-71.07h-10.4v-12.72h33.41v12.72h-10.51v71.07h10.51v12.72Z"/>
<path class="cls-2" d="M269.53,160.63l-25.22-53.23-8.52-20.6h-.33v73.84h-11.95v-96.51h14.05l25.11,53.23,8.52,20.6h.33v-73.83h11.95v96.51h-13.94Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View file

@ -0,0 +1,39 @@
import heroImage from "../assets/hero-room.jpg";
const AboutHero = () => {
return (
<section className="relative min-h-screen flex items-center justify-center overflow-hidden">
<div className="absolute inset-0">
<img
src={heroImage}
alt="Luxury interior journey since 1989"
className="w-full h-full object-cover"
/>
<div className="absolute inset-0 bg-black/20"></div>
</div>
<div className="relative z-10 text-center text-white px-4">
<h1 className="text-7xl md:text-8xl font-light mb-8 tracking-wide">
our journey
</h1>
<div className="absolute top-1/2 right-1/4 transform -translate-y-1/2 bg-white/10 backdrop-blur-sm border border-white/20 rounded-2xl p-8 text-white">
<div className="text-6xl font-light mb-2">1989</div>
<p className="text-sm opacity-90">Our founding year</p>
<p className="text-xs opacity-70 mt-2">Creating memorable<br />experiences since</p>
</div>
</div>
<div className="absolute bottom-8 left-1/2 transform -translate-x-1/2 flex gap-2">
{[1, 2, 3, 4, 5].map((dot, index) => (
<div
key={index}
className={`w-2 h-2 rounded-full ${index === 0 ? 'bg-white' : 'bg-white/30'}`}
/>
))}
</div>
</section>
);
};
export default AboutHero;

View file

@ -0,0 +1,91 @@
import { Calendar, Users, Wifi, Car } from "lucide-react";
import accommodation1 from "../assets/accommodation-1.jpg";
import accommodation2 from "../assets/accommodation-2.jpg";
const AccommodationSection = () => {
const accommodations = [
{
id: 1,
image: accommodation1,
title: "Luxury Downtown Suite",
description: "Spacious suite in the heart of the city with panoramic views and premium amenities.",
price: "€500",
amenities: ["Free WiFi", "Parking", "Room Service", "City View"],
rating: 4.8,
reviews: 124,
},
{
id: 2,
image: accommodation2,
title: "Modern Business Hotel",
description: "Contemporary accommodation perfect for business travelers with modern facilities.",
price: "€500",
amenities: ["Business Center", "Gym", "Restaurant", "Airport Shuttle"],
rating: 4.9,
reviews: 89,
},
];
return (
<section className="py-16 lg:py-24">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
{/* Section Title */}
<div className="text-center mb-16">
<h2 className="text-section-title text-center">accommodation</h2>
</div>
{/* Accommodation Cards */}
<div className="space-y-12">
{accommodations.map((item, index) => (
<div key={item.id} className="grid lg:grid-cols-2 gap-8 lg:gap-12 items-center">
{/* Image */}
<div className={`order-1 ${index % 2 === 1 ? 'lg:order-2' : ''}`}>
<div className="relative rounded-2xl overflow-hidden shadow-[var(--shadow-card)] group">
<img
src={item.image}
alt={item.title}
className="w-full h-[400px] object-cover group-hover:scale-105 transition-transform duration-700"
/>
<div className="absolute top-4 right-4 bg-card/95 backdrop-blur-sm rounded-lg px-3 py-1">
<span className="text-sm font-medium"> {item.rating}</span>
</div>
</div>
</div>
{/* Content */}
<div className={`order-2 space-y-6 ${index % 2 === 1 ? 'lg:order-1' : ''}`}>
<div className="space-y-4">
<h3 className="text-3xl font-light">{item.title}</h3>
<p className="text-muted-foreground text-lg leading-relaxed">{item.description}</p>
</div>
{/* Amenities */}
<div className="grid grid-cols-2 gap-3">
{item.amenities.map((amenity) => (
<div key={amenity} className="flex items-center space-x-2 text-sm">
<div className="w-2 h-2 bg-secondary rounded-full" />
<span>{amenity}</span>
</div>
))}
</div>
{/* Price and Booking */}
<div className="flex items-center justify-between">
<div className="space-y-1">
<div className="text-3xl font-medium">{item.price}<span className="text-lg text-muted-foreground">/night</span></div>
<div className="text-sm text-muted-foreground">{item.reviews} reviews</div>
</div>
<button className="btn-primary">
Book Now
</button>
</div>
</div>
</div>
))}
</div>
</div>
</section>
);
};
export default AccommodationSection;

View file

@ -0,0 +1,77 @@
import { ArrowRight } from "lucide-react";
import room1 from "../assets/room-1.jpg";
import room2 from "../assets/room-2.jpg";
import room3 from "../assets/room-3.jpg";
const BrandWorlds = () => {
const worlds = [
{
id: 1,
image: room1,
title: "style2own",
description: "Für kreative, lifestyle-orientierte Wohnkonzepte und moderne Lebensräume.",
link: "/about"
},
{
id: 2,
image: room2,
title: "stileigentum",
description: "Für exklusive Premium-Immobilien und zeitlose Eleganz mit höchsten Ansprüchen.",
link: "/rooms"
},
{
id: 3,
image: room3,
title: "B2A Export",
description: "Unser B2B-Arm für den Export von europäischem Design in die USA.",
link: "/partner"
}
];
return (
<section className="section-padding">
<div className="container-padding">
{/* Section Title */}
<div className="text-center mb-16">
<h2 className="text-section-title">Unsere Markenwelten</h2>
<p className="text-large text-muted-foreground mt-4 max-w-2xl mx-auto">
Entdecken Sie die Welten von B2In drei Bereiche, ein Ökosystem.
</p>
</div>
{/* Brand Cards */}
<div className="grid md:grid-cols-3 gap-8">
{worlds.map((world) => (
<div key={world.id} className="card-elevated overflow-hidden group hover:shadow-[var(--shadow-elevated)] transition-all duration-300">
<div className="relative">
<img
src={world.image}
alt={world.title}
className="w-full h-64 object-cover group-hover:scale-105 transition-transform duration-500"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/40 to-transparent" />
</div>
<div className="p-6 spacing-small">
<h3 className="text-xl font-medium">{world.title}</h3>
<p className="text-muted-foreground leading-relaxed">
{world.description}
</p>
<a
href={world.link}
className="inline-flex items-center gap-2 text-secondary font-medium hover:gap-3 transition-all duration-300"
>
Mehr erfahren
<ArrowRight className="w-4 h-4" />
</a>
</div>
</div>
))}
</div>
</div>
</section>
);
};
export default BrandWorlds;

View file

@ -0,0 +1,119 @@
import { TrendingUp, Clock, Target, Award } from "lucide-react";
const BrokerSection = () => {
const benefits = [
{
icon: TrendingUp,
title: "Lifetime-Vergütungsmodell",
description: "Kontinuierliche Provisionen durch langfristige Kundenbeziehungen und wiederkehrende Geschäfte"
},
{
icon: Clock,
title: "Schnellere Vermarktung",
description: "Durchdachte Wohnkonzepte reduzieren die Verkaufszeit und erhöhen die Erfolgschancen"
},
{
icon: Target,
title: "Qualifizierte Leads",
description: "Vorgefilterte, interessierte Kunden durch das B2In-Portal und Premium-Mitgliedschaften"
},
{
icon: Award,
title: "Premium-Positioning",
description: "Exklusive Vermarktung hochwertiger Wohnkonzepte für anspruchsvolle Zielgruppen"
}
];
return (
<section className="py-20 px-4 bg-background">
<div className="max-w-7xl mx-auto">
<div className="grid lg:grid-cols-2 gap-16 items-center">
<div className="relative order-2 lg:order-1">
<div className="card-elevated rounded-3xl p-12">
<div className="text-center mb-8">
<div className="w-20 h-20 mx-auto rounded-full bg-gradient-to-br from-primary to-primary/60 flex items-center justify-center mb-6">
<TrendingUp className="w-10 h-10 text-white" />
</div>
<h3 className="text-2xl font-semibold text-foreground mb-4">
Lifetime-Vergütung
</h3>
</div>
<div className="space-y-6">
<div className="bg-accent/30 rounded-xl p-6">
<div className="flex justify-between items-center mb-2">
<span className="text-sm text-muted-foreground">Erstverkauf</span>
<span className="text-lg font-semibold text-primary">3.5%</span>
</div>
<div className="w-full bg-accent/50 rounded-full h-2">
<div className="bg-primary h-2 rounded-full w-[35%]"></div>
</div>
</div>
<div className="bg-accent/30 rounded-xl p-6">
<div className="flex justify-between items-center mb-2">
<span className="text-sm text-muted-foreground">Folgegeschäfte</span>
<span className="text-lg font-semibold text-primary">1.5%</span>
</div>
<div className="w-full bg-accent/50 rounded-full h-2">
<div className="bg-primary h-2 rounded-full w-[60%]"></div>
</div>
</div>
<div className="bg-primary/10 rounded-xl p-6">
<div className="flex justify-between items-center mb-2">
<span className="text-sm font-medium text-foreground">Lifetime Value</span>
<span className="text-xl font-bold text-primary"></span>
</div>
<p className="text-xs text-muted-foreground">
Kontinuierliche Einnahmen durch Kundenbeziehung
</p>
</div>
</div>
</div>
</div>
<div className="space-y-8 order-1 lg:order-2">
<div>
<div className="inline-flex items-center gap-2 bg-primary/10 text-primary px-4 py-2 rounded-full text-sm font-medium mb-6">
<TrendingUp className="w-4 h-4" />
Für Makler
</div>
<h2 className="text-4xl lg:text-5xl font-light text-foreground mb-6">
Nachhaltiger <span className="text-primary">Erfolg</span> durch Innovation
</h2>
<p className="text-xl text-muted-foreground leading-relaxed">
Unser revolutionäres Lifetime-Vergütungsmodell belohnt langfristige
Kundenbeziehungen. Durch durchdachte Wohnkonzepte vermarkten Sie
Immobilien nicht nur schneller, sondern bauen nachhaltige
Einnahmequellen auf.
</p>
</div>
<div className="space-y-6">
{benefits.map((benefit, index) => (
<div key={index} className="flex gap-4">
<div className="flex-shrink-0 w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center">
<benefit.icon className="w-6 h-6 text-primary" />
</div>
<div>
<h3 className="text-lg font-semibold text-foreground mb-2">
{benefit.title}
</h3>
<p className="text-muted-foreground">
{benefit.description}
</p>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</section>
);
};
export default BrokerSection;

View file

@ -0,0 +1,25 @@
const CommitmentSection = () => {
return (
<section className="py-16 px-4 text-center">
<div className="max-w-4xl mx-auto">
<div className="mb-8">
<div className="inline-flex items-center gap-2 bg-primary text-primary-foreground px-4 py-2 rounded-full text-sm font-medium mb-6">
4.9 Rating
</div>
</div>
<h2 className="text-section-title text-foreground mb-6">
Committed to excellence, ensuring <br />
<span className="text-secondary">enduring comfort</span> and creating lasting <br />
memories
</h2>
<p className="text-muted-foreground text-lg">
Robert Wilson
</p>
</div>
</section>
);
};
export default CommitmentSection;

View file

@ -0,0 +1,83 @@
import accommodationImage2 from "../assets/accommodation-2.jpg";
const DarkStatsSection = () => {
return (
<section className="section-dark py-20 px-4">
<div className="max-w-7xl mx-auto">
<div className="grid lg:grid-cols-2 gap-16 items-center">
<div className="space-y-12">
<div className="grid grid-cols-2 gap-8">
<div>
<div className="text-6xl font-light text-[hsl(var(--dark-foreground))] mb-2">17+</div>
<p className="text-[hsl(var(--dark-muted))] text-sm">Years of Experience</p>
</div>
<div>
<div className="text-6xl font-light text-[hsl(var(--dark-foreground))] mb-2">2M</div>
<p className="text-[hsl(var(--dark-muted))] text-sm">Happy Guests</p>
</div>
</div>
<div className="space-y-6">
<h3 className="text-3xl font-light text-[hsl(var(--dark-foreground))]">
Economically Sound and Well-<br />
<span className="text-secondary">Friendly Service</span> for<br />
Families and Their<br />
Precious Belongings
</h3>
<p className="text-[hsl(var(--dark-muted))] leading-relaxed">
We understand that every family is unique, which is why we offer personalized
services tailored to meet your specific needs. From luxury amenities to
budget-friendly options, we ensure every guest feels valued and comfortable.
</p>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 pt-8">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-secondary rounded-full flex items-center justify-center">
<span className="text-primary text-sm"></span>
</div>
<div>
<p className="text-[hsl(var(--dark-foreground))] font-medium text-sm">Top Consultation</p>
<p className="text-[hsl(var(--dark-muted))] text-xs">Expert guidance</p>
</div>
</div>
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-secondary rounded-full flex items-center justify-center">
<span className="text-primary text-sm"></span>
</div>
<div>
<p className="text-[hsl(var(--dark-foreground))] font-medium text-sm">Finest Meal</p>
<p className="text-[hsl(var(--dark-muted))] text-xs">Gourmet dining</p>
</div>
</div>
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-secondary rounded-full flex items-center justify-center">
<span className="text-primary text-sm"></span>
</div>
<div>
<p className="text-[hsl(var(--dark-foreground))] font-medium text-sm">Easy Tax Reduction</p>
<p className="text-[hsl(var(--dark-muted))] text-xs">Cost-effective</p>
</div>
</div>
</div>
</div>
</div>
<div className="relative">
<div className="card-elevated bg-[hsl(var(--dark-muted))] p-0 overflow-hidden rounded-3xl">
<img
src={accommodationImage2}
alt="Luxury interior design"
className="w-full h-96 object-cover"
/>
</div>
</div>
</div>
</div>
</section>
);
};
export default DarkStatsSection;

View file

@ -0,0 +1,99 @@
import { Star, MapPin, Calendar } from "lucide-react";
import room3 from "../assets/room-3.jpg";
const DealSection = () => {
return (
<section className="py-16 lg:py-24">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
{/* Section Title */}
<div className="text-center mb-16">
<h2 className="text-section-title">steal deal</h2>
</div>
{/* Deal Card */}
<div className="max-w-4xl mx-auto">
<div className="card-elevated overflow-hidden">
<div className="grid lg:grid-cols-2">
{/* Image */}
<div className="relative">
<img
src={room3}
alt="Special deal accommodation"
className="w-full h-80 lg:h-full object-cover"
/>
<div className="absolute top-4 left-4 bg-red-500 text-white px-3 py-1 rounded-lg text-sm font-medium">
Limited Time
</div>
<div className="absolute top-4 right-4 bg-card/95 backdrop-blur-sm rounded-lg px-3 py-1">
<div className="flex items-center space-x-1">
<Star className="w-4 h-4 fill-yellow-400 text-yellow-400" />
<span className="text-sm font-medium">4.8</span>
</div>
</div>
</div>
{/* Content */}
<div className="p-8 lg:p-12 space-y-6">
<div className="space-y-4">
<div className="flex items-center space-x-2 text-sm text-muted-foreground">
<MapPin className="w-4 h-4" />
<span>Downtown Premium Location</span>
</div>
<h3 className="text-2xl lg:text-3xl font-light">
Luxury Suite Package
</h3>
<p className="text-muted-foreground leading-relaxed">
Exclusive weekend package including breakfast, spa access, and city tour.
Perfect for romantic getaways or special celebrations.
</p>
</div>
{/* Price */}
<div className="space-y-2">
<div className="flex items-baseline space-x-2">
<span className="text-3xl font-bold text-red-500">$100</span>
<span className="text-lg text-muted-foreground line-through">$250</span>
<span className="text-sm text-muted-foreground">/night</span>
</div>
<div className="text-sm text-green-600 font-medium">Save 60% this weekend!</div>
</div>
{/* Features */}
<div className="space-y-3">
<div className="flex items-center space-x-2 text-sm">
<div className="w-2 h-2 bg-green-500 rounded-full" />
<span>Free breakfast included</span>
</div>
<div className="flex items-center space-x-2 text-sm">
<div className="w-2 h-2 bg-green-500 rounded-full" />
<span>Complimentary spa access</span>
</div>
<div className="flex items-center space-x-2 text-sm">
<div className="w-2 h-2 bg-green-500 rounded-full" />
<span>Free cancellation up to 24h</span>
</div>
</div>
{/* Booking */}
<div className="space-y-4">
<div className="flex items-center space-x-2 text-sm text-muted-foreground">
<Calendar className="w-4 h-4" />
<span>Available this weekend only</span>
</div>
<button className="w-full btn-primary">
Book This Deal
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
};
export default DealSection;

View file

@ -0,0 +1,52 @@
import room2Image from "../assets/room-2.jpg";
const DedicationSection = () => {
return (
<section className="py-20 px-4 bg-background">
<div className="max-w-7xl mx-auto">
<div className="grid lg:grid-cols-2 gap-16 items-center">
<div className="relative">
<div className="card-elevated overflow-hidden rounded-3xl">
<img
src={room2Image}
alt="Dedicated service excellence"
className="w-full h-96 object-cover"
/>
</div>
</div>
<div className="space-y-8">
<div className="text-8xl font-light text-foreground">
4510<span className="text-secondary">+</span>
</div>
<div className="space-y-6">
<h3 className="text-3xl font-light text-foreground">
Dedicated to exceeding your expectations, providing comfort and satisfaction for memorable experiences
</h3>
<p className="text-muted-foreground leading-relaxed">
Our commitment goes beyond just providing accommodation. We strive to create
moments that matter, ensuring every guest leaves with cherished memories
and a desire to return.
</p>
<div className="grid grid-cols-2 gap-6 pt-6">
<div className="space-y-2">
<div className="text-2xl font-light text-foreground">98%</div>
<p className="text-muted-foreground text-sm">Guest satisfaction rate</p>
</div>
<div className="space-y-2">
<div className="text-2xl font-light text-foreground">24/7</div>
<p className="text-muted-foreground text-sm">Dedicated support</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
};
export default DedicationSection;

View file

@ -0,0 +1,179 @@
import { Zap, Cloud, Shield, Layers, Database, Cpu } from "lucide-react";
const DigitalCore = () => {
const features = [
{
icon: Cloud,
title: "Cloud-Native Architektur",
description: "Skalierbare, sichere und hochverfügbare Infrastruktur für alle Ecosystem-Teilnehmer"
},
{
icon: Shield,
title: "Enterprise Security",
description: "Modernste Sicherheitsstandards und Datenschutz für vertrauliche Geschäftsdaten"
},
{
icon: Layers,
title: "API-First Design",
description: "Nahtlose Integration und Erweiterbarkeit für alle Plattform-Komponenten"
},
{
icon: Database,
title: "Real-Time Analytics",
description: "Live-Einblicke und Datenanalyse für optimierte Geschäftsentscheidungen"
},
{
icon: Cpu,
title: "KI-gestützte Automation",
description: "Intelligente Prozessoptimierung und personalisierte Nutzererfahrungen"
},
{
icon: Zap,
title: "Performance Excellence",
description: "Blitzschnelle Ladezeiten und responsive Benutzeroberflächen"
}
];
return (
<section className="py-20 px-4 bg-background">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<div className="inline-flex items-center gap-2 bg-primary/10 text-primary px-4 py-2 rounded-full text-sm font-medium mb-6">
<Zap className="w-4 h-4" />
Das digitale Herzstück
</div>
<h2 className="text-4xl lg:text-5xl font-light text-foreground mb-6">
B2In <span className="text-primary">Portal</span>
</h2>
<p className="text-xl text-muted-foreground max-w-3xl mx-auto leading-relaxed">
Unsere zentrale technologische Plattform verbindet alle Ecosystem-Teilnehmer
nahtlos miteinander. Modernste Technologie trifft auf intuitive Bedienung
und schafft einzigartige digitale Erlebnisse.
</p>
</div>
<div className="grid lg:grid-cols-2 gap-16 items-center mb-16">
<div className="relative">
<div className="card-elevated rounded-3xl overflow-hidden">
<div className="bg-gradient-to-br from-primary/10 via-primary/5 to-background p-12">
<div className="text-center mb-8">
<div className="w-24 h-24 mx-auto rounded-full bg-primary flex items-center justify-center mb-6">
<Zap className="w-12 h-12 text-white" />
</div>
<h3 className="text-2xl font-semibold text-foreground">
Zentrale Plattform
</h3>
</div>
<div className="space-y-4">
<div className="flex items-center gap-3 bg-background/50 rounded-xl p-4">
<div className="w-8 h-8 rounded-full bg-green-500 flex items-center justify-center">
<div className="w-3 h-3 rounded-full bg-white"></div>
</div>
<div>
<p className="font-medium text-sm">Endkunden Portal</p>
<p className="text-xs text-muted-foreground">Online & Verfügbar</p>
</div>
</div>
<div className="flex items-center gap-3 bg-background/50 rounded-xl p-4">
<div className="w-8 h-8 rounded-full bg-green-500 flex items-center justify-center">
<div className="w-3 h-3 rounded-full bg-white"></div>
</div>
<div>
<p className="font-medium text-sm">Makler Dashboard</p>
<p className="text-xs text-muted-foreground">Online & Verfügbar</p>
</div>
</div>
<div className="flex items-center gap-3 bg-background/50 rounded-xl p-4">
<div className="w-8 h-8 rounded-full bg-green-500 flex items-center justify-center">
<div className="w-3 h-3 rounded-full bg-white"></div>
</div>
<div>
<p className="font-medium text-sm">Lieferanten Plattform</p>
<p className="text-xs text-muted-foreground">Online & Verfügbar</p>
</div>
</div>
<div className="flex items-center gap-3 bg-background/50 rounded-xl p-4">
<div className="w-8 h-8 rounded-full bg-green-500 flex items-center justify-center">
<div className="w-3 h-3 rounded-full bg-white"></div>
</div>
<div>
<p className="font-medium text-sm">B2A Integration</p>
<p className="text-xs text-muted-foreground">Online & Verfügbar</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="space-y-8">
<div>
<h3 className="text-3xl font-light text-foreground mb-6">
Technische <span className="text-primary">Excellence</span>
</h3>
<p className="text-lg text-muted-foreground leading-relaxed">
Das B2In-Portal ist mehr als nur eine Software es ist das
technologische Rückgrat unseres gesamten Ecosystems. Entwickelt
mit modernsten Standards für Sicherheit, Performance und
Benutzerfreundlichkeit.
</p>
</div>
<div className="grid gap-4">
<div className="flex items-center gap-3 bg-accent/30 rounded-xl p-4">
<Shield className="w-6 h-6 text-primary" />
<div>
<p className="font-semibold text-foreground">99.9% Uptime</p>
<p className="text-sm text-muted-foreground">Garantierte Verfügbarkeit</p>
</div>
</div>
<div className="flex items-center gap-3 bg-accent/30 rounded-xl p-4">
<Cpu className="w-6 h-6 text-primary" />
<div>
<p className="font-semibold text-foreground">&lt; 200ms Response</p>
<p className="text-sm text-muted-foreground">Blitzschnelle Performance</p>
</div>
</div>
<div className="flex items-center gap-3 bg-accent/30 rounded-xl p-4">
<Database className="w-6 h-6 text-primary" />
<div>
<p className="font-semibold text-foreground">ISO 27001 Certified</p>
<p className="text-sm text-muted-foreground">Enterprise Security</p>
</div>
</div>
</div>
</div>
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{features.map((feature, index) => (
<div key={index} className="card-elevated rounded-2xl p-8">
<div className="w-14 h-14 rounded-xl bg-primary/10 flex items-center justify-center mb-6">
<feature.icon className="w-7 h-7 text-primary" />
</div>
<h3 className="text-xl font-semibold text-foreground mb-4">
{feature.title}
</h3>
<p className="text-muted-foreground leading-relaxed">
{feature.description}
</p>
</div>
))}
</div>
</div>
</section>
);
};
export default DigitalCore;

View file

@ -0,0 +1,55 @@
import { Globe, Building, Users } from "lucide-react";
const EcosystemCore = () => {
const pillars = [
{
icon: Globe,
title: "Globaler Immobilienhandel",
description: "Direkter Zugang zu exklusiven Investments und internationalen Märkten mit professioneller Begleitung."
},
{
icon: Building,
title: "Kuratierte Wohnkonzepte",
description: "Wertsteigerung durch professionelles Staging und durchdachte Design-Konzepte für Ihre Immobilien."
},
{
icon: Users,
title: "Starkes Partnernetzwerk",
description: "Ein B2B-Arm für Lieferanten, Makler & den US-Export mit fairen Konditionen und digitalem Support."
}
];
return (
<section className="section-padding">
<div className="container-padding">
{/* Section Title */}
<div className="text-center mb-16">
<h2 className="text-section-title">Ein Ökosystem, drei Stärken</h2>
<p className="text-large text-muted-foreground mt-4 max-w-2xl mx-auto">
Wir schaffen Synergien, die den Markt revolutionieren.
</p>
</div>
{/* Pillars Grid */}
<div className="grid md:grid-cols-3 gap-8 lg:gap-12">
{pillars.map((pillar, index) => (
<div key={index} className="text-center spacing-content group">
<div className="mx-auto w-20 h-20 bg-secondary/10 rounded-2xl flex items-center justify-center group-hover:bg-secondary/20 transition-colors duration-300">
<pillar.icon className="w-10 h-10 text-secondary" />
</div>
<div className="spacing-small">
<h3 className="text-xl font-medium">{pillar.title}</h3>
<p className="text-muted-foreground leading-relaxed">
{pillar.description}
</p>
</div>
</div>
))}
</div>
</div>
</section>
);
};
export default EcosystemCore;

View file

@ -0,0 +1,113 @@
import { Network, Users, Building2, Zap } from "lucide-react";
const EcosystemHero = () => {
return (
<section className="min-h-screen flex items-center relative overflow-hidden">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-20 bg-[hsl(var(--hero-container))] rounded-[20px] w-[95%]">
<div className="grid lg:grid-cols-2 gap-16 items-center">
<div className="space-y-8">
<h1 className="text-5xl lg:text-7xl font-light text-foreground">
B2In <span className="text-primary">Ecosystem</span>
</h1>
<p className="text-xl lg:text-2xl text-muted-foreground leading-relaxed">
Ein intelligentes Netzwerk, das Endkunden, Makler, Lieferanten und
Technologie nahtlos miteinander verbindet. Jeder Teilnehmer profitiert
vom gesamten System und schafft gemeinsam außergewöhnliche Immobilienerlebnisse.
</p>
<div className="grid grid-cols-2 gap-6">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center">
<Users className="w-6 h-6 text-primary" />
</div>
<div>
<p className="font-semibold text-foreground">Endkunden</p>
<p className="text-sm text-muted-foreground">Exklusive Erlebnisse</p>
</div>
</div>
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center">
<Building2 className="w-6 h-6 text-primary" />
</div>
<div>
<p className="font-semibold text-foreground">Makler</p>
<p className="text-sm text-muted-foreground">Lifetime-Vergütung</p>
</div>
</div>
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center">
<Network className="w-6 h-6 text-primary" />
</div>
<div>
<p className="font-semibold text-foreground">Lieferanten</p>
<p className="text-sm text-muted-foreground">Kuratierte Plattform</p>
</div>
</div>
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center">
<Zap className="w-6 h-6 text-primary" />
</div>
<div>
<p className="font-semibold text-foreground">Technologie</p>
<p className="text-sm text-muted-foreground">Digitales Herzstück</p>
</div>
</div>
</div>
</div>
<div className="relative">
<div className="card-elevated rounded-3xl p-12 bg-background/80 backdrop-blur-sm">
<div className="space-y-8">
{/* Central Hub */}
<div className="text-center">
<div className="w-24 h-24 mx-auto rounded-full bg-primary flex items-center justify-center mb-4">
<Network className="w-12 h-12 text-white" />
</div>
<h3 className="text-xl font-semibold text-foreground">B2In Portal</h3>
<p className="text-sm text-muted-foreground">Zentrale Plattform</p>
</div>
{/* Connection Lines */}
<div className="grid grid-cols-2 gap-8">
<div className="text-center">
<div className="w-16 h-16 mx-auto rounded-full bg-accent flex items-center justify-center mb-3">
<Users className="w-8 h-8 text-accent-foreground" />
</div>
<p className="font-medium text-sm">Endkunden</p>
</div>
<div className="text-center">
<div className="w-16 h-16 mx-auto rounded-full bg-accent flex items-center justify-center mb-3">
<Building2 className="w-8 h-8 text-accent-foreground" />
</div>
<p className="font-medium text-sm">Makler</p>
</div>
<div className="text-center">
<div className="w-16 h-16 mx-auto rounded-full bg-accent flex items-center justify-center mb-3">
<Network className="w-8 h-8 text-accent-foreground" />
</div>
<p className="font-medium text-sm">Lieferanten</p>
</div>
<div className="text-center">
<div className="w-16 h-16 mx-auto rounded-full bg-accent flex items-center justify-center mb-3">
<Zap className="w-8 h-8 text-accent-foreground" />
</div>
<p className="font-medium text-sm">B2A</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
};
export default EcosystemHero;

View file

@ -0,0 +1,58 @@
const EcosystemStats = () => {
const stats = [
{
number: "1,745,678+",
label: "Aktive Nutzer im Ecosystem",
description: "Wachsende Community von Endkunden, Maklern und Lieferanten"
},
{
number: "4,510+",
label: "Erfolgreiche Projekte",
description: "Realisierte Immobilienprojekte durch unser Netzwerk"
},
{
number: "98%",
label: "Zufriedenheitsrate",
description: "Kundenzufriedenheit across alle Ecosystem-Teilnehmer"
},
{
number: "24/7",
label: "Platform Verfügbarkeit",
description: "Kontinuierliche Verfügbarkeit der digitalen Infrastruktur"
}
];
return (
<section className="py-20 px-4 bg-background">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<h2 className="text-4xl lg:text-5xl font-light text-foreground mb-6">
Unser <span className="text-primary">Ecosystem</span> in Zahlen
</h2>
<p className="text-xl text-muted-foreground max-w-3xl mx-auto">
Zahlen, die die Stärke und das Vertrauen in unser vernetztes
Geschäftsmodell widerspiegeln.
</p>
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-8">
{stats.map((stat, index) => (
<div key={index} className="card-elevated rounded-2xl p-8 text-center">
<div className="text-4xl lg:text-5xl font-bold text-primary mb-4">
{stat.number}
</div>
<h3 className="text-lg font-semibold text-foreground mb-3">
{stat.label}
</h3>
<p className="text-sm text-muted-foreground leading-relaxed">
{stat.description}
</p>
</div>
))}
</div>
</div>
</section>
);
};
export default EcosystemStats;

View file

@ -0,0 +1,94 @@
import { CreditCard, Star, Home, Shield } from "lucide-react";
const EndCustomerSection = () => {
const benefits = [
{
icon: CreditCard,
title: "Exklusive Login-Karte",
description: "Personalisierter Zugang zu ausgewählten Immobilienerlebnissen und Premium-Services"
},
{
icon: Star,
title: "Personalisierte Erlebniswelt",
description: "Maßgeschneiderte Immobilienangebote basierend auf individuellen Präferenzen und Bedürfnissen"
},
{
icon: Home,
title: "Kuratierte Wohnkonzepte",
description: "Hochwertige, durchdachte Immobilienlösungen von verifizierten Partnern"
},
{
icon: Shield,
title: "Qualitätsgarantie",
description: "Geprüfte Anbieter und standardisierte Qualitätsprozesse für maximale Sicherheit"
}
];
return (
<section className="py-20 px-4 bg-accent/30">
<div className="max-w-7xl mx-auto">
<div className="grid lg:grid-cols-2 gap-16 items-center">
<div className="space-y-8">
<div>
<div className="inline-flex items-center gap-2 bg-primary/10 text-primary px-4 py-2 rounded-full text-sm font-medium mb-6">
<Star className="w-4 h-4" />
Für Endkunden
</div>
<h2 className="text-4xl lg:text-5xl font-light text-foreground mb-6">
Exklusive <span className="text-primary">Erlebnisse</span> für Sie
</h2>
<p className="text-xl text-muted-foreground leading-relaxed">
Mit Ihrer persönlichen Login-Karte erhalten Sie Zugang zu einer
einzigartigen Erlebniswelt, die speziell auf Ihre Wohnwünsche
und Lebensstil abgestimmt ist. Entdecken Sie kuratierte Immobilien
und Services, die sonst nicht verfügbar sind.
</p>
</div>
<div className="space-y-6">
{benefits.map((benefit, index) => (
<div key={index} className="flex gap-4">
<div className="flex-shrink-0 w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center">
<benefit.icon className="w-6 h-6 text-primary" />
</div>
<div>
<h3 className="text-lg font-semibold text-foreground mb-2">
{benefit.title}
</h3>
<p className="text-muted-foreground">
{benefit.description}
</p>
</div>
</div>
))}
</div>
</div>
<div className="relative">
<div className="card-elevated rounded-3xl overflow-hidden">
<div className="bg-gradient-to-br from-primary/20 to-primary/5 p-12 h-96 flex flex-col justify-center items-center text-center">
<div className="w-24 h-24 rounded-full bg-primary/20 backdrop-blur-sm flex items-center justify-center mb-6">
<CreditCard className="w-12 h-12 text-primary" />
</div>
<h3 className="text-2xl font-semibold text-foreground mb-4">
Login-Karte
</h3>
<p className="text-muted-foreground mb-6">
Ihr Schlüssel zu exklusiven Immobilienerlebnissen
</p>
<div className="bg-background/80 backdrop-blur-sm rounded-xl p-4 w-full max-w-xs">
<div className="text-sm text-muted-foreground mb-2">Mitgliedsnummer</div>
<div className="font-mono text-lg">B2IN-2024-VIP</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
};
export default EndCustomerSection;

View file

@ -0,0 +1,74 @@
import room1Image from "../assets/room-1.jpg";
const EssentialsSection = () => {
const essentials = [
{
title: "Premium Comfort",
description: "Luxurious bedding and furniture designed for ultimate relaxation and rest."
},
{
title: "Modern Amenities",
description: "State-of-the-art facilities including high-speed WiFi, smart TV, and climate control."
},
{
title: "Exceptional Service",
description: "24/7 concierge service to assist with all your needs and requests."
},
{
title: "Prime Location",
description: "Strategically located with easy access to major attractions and business districts."
},
{
title: "Gourmet Dining",
description: "World-class restaurant and room service featuring international and local cuisine."
},
{
title: "Wellness Facilities",
description: "Spa services, fitness center, and recreational facilities for your well-being."
}
];
return (
<section className="py-20 px-4 bg-accent">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<h2 className="text-6xl font-light text-foreground mb-4">essentials</h2>
<p className="text-muted-foreground text-lg">
Everything you need for a perfect stay
</p>
</div>
<div className="grid lg:grid-cols-2 gap-16 items-center">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{essentials.map((essential, index) => (
<div key={index} className="space-y-3">
<h3 className="text-xl font-medium text-foreground">
{essential.title}
</h3>
<p className="text-muted-foreground text-sm leading-relaxed">
{essential.description}
</p>
</div>
))}
</div>
<div className="relative">
<div className="card-elevated overflow-hidden rounded-3xl">
<img
src={room1Image}
alt="Essential amenities showcase"
className="w-full h-96 object-cover"
/>
</div>
<div className="absolute top-8 right-8 bg-white/10 backdrop-blur-sm border border-white/20 rounded-lg p-4 text-white">
<div className="text-2xl font-light">Premium</div>
<p className="text-xs opacity-90">Quality assured</p>
</div>
</div>
</div>
</div>
</section>
);
};
export default EssentialsSection;

View file

@ -0,0 +1,29 @@
const FinalCTA = () => {
return (
<section className="section-dark section-padding">
<div className="container-narrow text-center">
<div className="spacing-section">
<h2 className="text-4xl lg:text-5xl font-light leading-tight">
Gestalten Sie die Zukunft <span className="text-secondary">mit uns</span>
</h2>
<p className="text-large text-dark-muted max-w-2xl mx-auto leading-relaxed">
Werden Sie Teil der Zukunft des Wohnens und Investierens.
Entdecken Sie die Möglichkeiten unseres globalen Ökosystems.
</p>
<div className="pt-4">
<a
href="/contact"
className="btn-accent px-8 py-4 text-lg"
>
Kontakt aufnehmen
</a>
</div>
</div>
</div>
</section>
);
};
export default FinalCTA;

View file

@ -0,0 +1,26 @@
const FinalCommitment = () => {
return (
<section className="section-dark py-20 px-4 text-center">
<div className="max-w-4xl mx-auto">
<h2 className="text-5xl md:text-6xl font-light text-[hsl(var(--dark-foreground))] leading-tight mb-8">
We're committed to <br />
your <span className="text-secondary">comfort</span> and <br />
<span className="text-secondary">satisfaction</span> for <br />
unforgettable <br />
experiences
</h2>
<div className="flex justify-center mb-8">
<div className="w-16 h-px bg-secondary"></div>
</div>
<p className="text-[hsl(var(--dark-muted))] text-lg">
Robert Wilson <br />
<span className="text-sm">General Manager</span>
</p>
</div>
</section>
);
};
export default FinalCommitment;

View file

@ -0,0 +1,98 @@
import b2inLogo from "../assets/b2in-logo-negative.svg";
const Footer = () => {
return (
<footer className="section-dark section-padding">
<div className="container-padding">
{/* Main Content */}
<div className="text-center spacing-section">
<div className="spacing-section">
<div className="flex items-center justify-center">
<img src={b2inLogo} alt="B2In Logo" className="h-12 w-auto" />
</div>
<div className="container-narrow spacing-content">
<h2 className="text-section-title text-[hsl(var(--dark-text))] leading-tight">
We're committed to your <span className="text-secondary">comfort</span> and <br />
<span className="text-secondary">satisfaction</span> for <br />
unforgettable experiences
</h2>
<p className="text-large text-dark-muted leading-relaxed max-w-2xl mx-auto">
Our dedicated team works around the clock to ensure every aspect of your stay
exceeds expectations. From booking to checkout, we're here for you.
</p>
</div>
<button className="btn-accent">
Start Your Journey
</button>
</div>
{/* Links */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-8 text-left max-w-4xl mx-auto">
<div className="spacing-small">
<h4 className="font-medium text-[hsl(var(--dark-text))]">Company</h4>
<div className="spacing-small text-dark-muted text-sm">
<a href="#" className="block hover:text-secondary transition-colors">About Us</a>
<a href="#" className="block hover:text-secondary transition-colors">Careers</a>
<a href="#" className="block hover:text-secondary transition-colors">Press</a>
<a href="#" className="block hover:text-secondary transition-colors">Blog</a>
</div>
</div>
<div className="spacing-small">
<h4 className="font-medium text-[hsl(var(--dark-text))]">Services</h4>
<div className="spacing-small text-dark-muted text-sm">
<a href="#" className="block hover:text-secondary transition-colors">Hotels</a>
<a href="#" className="block hover:text-secondary transition-colors">Apartments</a>
<a href="#" className="block hover:text-secondary transition-colors">Resorts</a>
<a href="#" className="block hover:text-secondary transition-colors">Villas</a>
</div>
</div>
<div className="spacing-small">
<h4 className="font-medium text-[hsl(var(--dark-text))]">Support</h4>
<div className="spacing-small text-dark-muted text-sm">
<a href="#" className="block hover:text-secondary transition-colors">Help Center</a>
<a href="#" className="block hover:text-secondary transition-colors">Contact Us</a>
<a href="#" className="block hover:text-secondary transition-colors">Safety</a>
<a href="#" className="block hover:text-secondary transition-colors">Cancellation</a>
</div>
</div>
<div className="spacing-small">
<h4 className="font-medium text-[hsl(var(--dark-text))]">Legal</h4>
<div className="spacing-small text-dark-muted text-sm">
<a href="#" className="block hover:text-secondary transition-colors">Privacy Policy</a>
<a href="#" className="block hover:text-secondary transition-colors">Terms of Service</a>
<a href="#" className="block hover:text-secondary transition-colors">Cookie Policy</a>
<a href="#" className="block hover:text-secondary transition-colors">Sitemap</a>
</div>
</div>
</div>
</div>
{/* Bottom Bar */}
<div className="border-t border-[hsl(var(--dark-muted))]/30 mt-12 pt-8">
<div className="flex flex-col md:flex-row justify-between items-center spacing-small md:space-y-0">
<div className="text-dark-muted text-sm">
© 2024 B2In. All rights reserved.
</div>
<div className="flex items-center space-x-6 text-dark-muted text-sm">
<a href="#" className="hover:text-secondary transition-colors">English</a>
<a href="#" className="hover:text-secondary transition-colors">EUR </a>
<div className="flex space-x-4">
<a href="#" className="hover:text-secondary transition-colors">Facebook</a>
<a href="#" className="hover:text-secondary transition-colors">Instagram</a>
<a href="#" className="hover:text-secondary transition-colors">Twitter</a>
</div>
</div>
</div>
</div>
</div>
</footer>
);
};
export default Footer;

View file

@ -0,0 +1,107 @@
import { Menu, X } from "lucide-react";
import { Link } from "react-router-dom";
import { Button } from "./ui/button";
import { useState } from "react";
import logoSvg from "../assets/b2in-logo-positive.svg";
const Header = () => {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const toggleMobileMenu = () => {
setIsMobileMenuOpen(!isMobileMenuOpen);
};
const closeMobileMenu = () => {
setIsMobileMenuOpen(false);
};
return (
<header className="fixed top-0 left-0 right-0 z-50 bg-background/80 backdrop-blur-sm border-b border-border">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16">
<Link to="/" className="flex items-center">
<img src={logoSvg} alt="B2IN Logo" className="h-8 w-auto" />
</Link>
<nav className="hidden md:flex items-center space-x-8">
<Link to="/" className="text-sm font-medium hover:text-secondary transition-colors">Home</Link>
<Link to="/services" className="text-sm font-medium hover:text-secondary transition-colors">Partner</Link>
<Link to="/ecosystem" className="text-sm font-medium hover:text-secondary transition-colors">Ecosystem</Link>
<Link to="/magazin" className="text-sm font-medium hover:text-secondary transition-colors">Magazin</Link>
<Link to="/about" className="text-sm font-medium hover:text-secondary transition-colors">About</Link>
<Link to="/contact" className="text-sm font-medium hover:text-secondary transition-colors">Contact</Link>
</nav>
<div className="flex items-center space-x-4">
<Button className="hidden md:block bg-secondary hover:bg-primary font-medium px-6 py-2 rounded-lg transition-all duration-200 shadow-md hover:shadow-accent-glow">
Portal Login
</Button>
<button
onClick={toggleMobileMenu}
className="md:hidden w-5 h-5 text-muted-foreground hover:text-foreground transition-colors"
>
{isMobileMenuOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
</button>
</div>
</div>
{/* Mobile Menu */}
{isMobileMenuOpen && (
<div className="md:hidden border-t border-border bg-background/95 backdrop-blur-sm">
<nav className="px-4 py-6 space-y-4">
<Link
to="/"
className="block text-sm font-medium hover:text-secondary transition-colors"
onClick={closeMobileMenu}
>
Home
</Link>
<Link
to="/services"
className="block text-sm font-medium hover:text-secondary transition-colors"
onClick={closeMobileMenu}
>
Partner
</Link>
<Link
to="/ecosystem"
className="block text-sm font-medium hover:text-secondary transition-colors"
onClick={closeMobileMenu}
>
Ecosystem
</Link>
<Link
to="/magazin"
className="block text-sm font-medium hover:text-secondary transition-colors"
onClick={closeMobileMenu}
>
Magazin
</Link>
<Link
to="/about"
className="block text-sm font-medium hover:text-secondary transition-colors"
onClick={closeMobileMenu}
>
About
</Link>
<Link
to="/contact"
className="block text-sm font-medium hover:text-secondary transition-colors"
onClick={closeMobileMenu}
>
Contact
</Link>
<div className="pt-4 border-t border-border">
<Button className="w-full bg-secondary hover:bg-primary font-medium px-6 py-2 rounded-lg transition-all duration-200">
Portal Login
</Button>
</div>
</nav>
</div>
)}
</div>
</header>
);
};
export default Header;

View file

@ -0,0 +1,61 @@
import heroImage from "../assets/hero-room.jpg";
const Hero = () => {
return (
<section className="min-h-screen flex items-center relative overflow-hidden">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-20 bg-[hsl(var(--hero-container))] rounded-[20px] w-[95%]">
<div className="grid lg:grid-cols-2 gap-12 lg:gap-16 items-center">
{/* Left Content */}
<div className="space-y-8">
<div className="space-y-6">
<h1 className="text-hero">
Connecting Design<br />
and <span className="text-secondary">Property</span>
</h1>
<p className="text-lg text-muted-foreground max-w-md leading-relaxed">
Das globale Ökosystem für Immobilieninvestoren, Makler und Designliebhaber.
</p>
</div>
<div className="flex flex-col sm:flex-row gap-4">
<button className="btn-primary">
Ecosystem entdecken
</button>
<button className="btn-secondary">
Partner werden
</button>
</div>
<div className="flex items-center space-x-8 text-sm text-muted-foreground">
<span>1.7M+ Nutzer</span>
<span></span>
<span>4.500+ Projekte</span>
<span></span>
<span>24/7 Platform</span>
</div>
</div>
{/* Right Image */}
<div className="relative">
<div className="relative rounded-3xl overflow-hidden shadow-[var(--shadow-elevated)]">
<img
src={heroImage}
alt="Modern international skyline showcasing architectural design"
className="w-full h-[600px] object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/20 to-transparent" />
</div>
{/* Floating info card */}
<div className="absolute bottom-6 left-6 bg-card/95 backdrop-blur-sm rounded-xl p-4 shadow-lg border border-border/50">
<div className="text-sm text-muted-foreground">B2In Ecosystem</div>
<div className="text-2xl font-medium">Global<span className="text-sm text-muted-foreground"> vernetzt</span></div>
</div>
</div>
</div>
</div>
</section>
);
};
export default Hero;

View file

@ -0,0 +1,69 @@
import room3Image from "../assets/room-3.jpg";
const HesitationsSection = () => {
const faqs = [
{
question: "What makes your accommodation special?",
answer: "Our unique blend of luxury, personalized service, and attention to detail sets us apart from others."
},
{
question: "Do you offer flexible booking options?",
answer: "Yes, we provide flexible booking with easy cancellation policies and modification options."
},
{
question: "What amenities are included?",
answer: "All rooms include premium amenities like high-speed WiFi, smart TV, climate control, and 24/7 room service."
},
{
question: "Is parking available?",
answer: "We offer complimentary valet parking for all our guests with secure underground facilities."
},
{
question: "Do you have dining options?",
answer: "Our award-winning restaurant offers gourmet dining with international and local cuisine options."
}
];
return (
<section className="section-dark py-20 px-4">
<div className="max-w-7xl mx-auto">
<div className="grid lg:grid-cols-2 gap-16 items-center">
<div className="space-y-8">
<h2 className="text-6xl font-light text-[hsl(var(--dark-foreground))]">
hesitations<span className="text-secondary">?</span>
</h2>
<div className="space-y-6">
{faqs.map((faq, index) => (
<div key={index} className="border-b border-[hsl(var(--dark-muted))]/20 pb-4">
<h3 className="text-lg font-medium text-[hsl(var(--dark-foreground))] mb-2">
{faq.question}
</h3>
<p className="text-[hsl(var(--dark-muted))] text-sm leading-relaxed">
{faq.answer}
</p>
</div>
))}
</div>
<button className="btn-secondary bg-secondary text-secondary-foreground hover:bg-secondary/90">
Contact Us
</button>
</div>
<div className="relative">
<div className="card-elevated bg-[hsl(var(--dark-muted))] p-0 overflow-hidden rounded-3xl">
<img
src={room3Image}
alt="Hesitations resolved with quality service"
className="w-full h-96 object-cover"
/>
</div>
</div>
</div>
</div>
</section>
);
};
export default HesitationsSection;

View file

@ -0,0 +1,29 @@
const JourneyStats = () => {
return (
<section className="py-20 px-4 bg-background">
<div className="max-w-7xl mx-auto text-center">
<div className="mb-12">
<div className="text-8xl md:text-9xl font-light text-foreground mb-4">
1,745,678<span className="text-secondary">+</span>
</div>
<p className="text-muted-foreground text-lg">
Happy guests who have experienced our hospitality
</p>
</div>
<div className="max-w-3xl mx-auto">
<h2 className="text-3xl font-light text-foreground mb-6">
Committed to surpassing expectations, ensuring comfort and satisfaction for memorable experiences
</h2>
<p className="text-muted-foreground leading-relaxed">
Since 1989, we have been dedicated to providing exceptional accommodation services
that exceed our guests' expectations. Every detail is carefully crafted to ensure
your stay is not just comfortable, but truly unforgettable.
</p>
</div>
</div>
</section>
);
};
export default JourneyStats;

View file

@ -0,0 +1,81 @@
import marcelImage from "../assets/marcel-scheibe.jpg";
import sarahImage from "../assets/sarah-mueller.jpg";
import thomasImage from "../assets/thomas-weber.jpg";
const LeadershipTeam = () => {
const team = [
{
name: "Marcel Scheibe",
position: "Gründer & CEO",
expertise: "Visionär für digitale Transformation und strategische Unternehmensführung.",
image: marcelImage
},
{
name: "Sarah Müller",
position: "Head of Operations",
expertise: "Expertin für Prozessoptimierung und operative Exzellenz in B2B-Umgebungen.",
image: sarahImage
},
{
name: "Thomas Weber",
position: "Head of Technology",
expertise: "Technologieführer mit Fokus auf innovative Konnektivitätslösungen.",
image: thomasImage
}
];
return (
<section className="py-20 px-4 bg-background">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<h2 className="text-4xl lg:text-5xl font-light text-foreground mb-6">
Das <span className="text-secondary">Führungsteam</span>
</h2>
<p className="text-muted-foreground text-lg max-w-2xl mx-auto">
Unser erfahrenes Team bringt jahrzehntelange Expertise in den Bereichen
Technologie, Operations und Geschäftsentwicklung mit.
</p>
</div>
<div className="grid md:grid-cols-3 gap-8">
{team.map((member, index) => (
<div key={index} className="group">
<div className="card-elevated rounded-3xl overflow-hidden hover-scale transition-all duration-300">
<div className="relative">
<img
src={member.image}
alt={`${member.name} - ${member.position}`}
className="w-full h-80 object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent"></div>
</div>
<div className="p-6 space-y-4">
<div>
<h3 className="text-xl font-semibold text-foreground mb-1">
{member.name}
</h3>
<p className="text-secondary font-medium text-sm">
{member.position}
</p>
</div>
<p className="text-muted-foreground text-sm leading-relaxed">
{member.expertise}
</p>
<div className="flex items-center gap-2 pt-2">
<div className="w-8 h-px bg-secondary"></div>
<span className="text-xs text-secondary font-medium">B2IN TEAM</span>
</div>
</div>
</div>
</div>
))}
</div>
</div>
</section>
);
};
export default LeadershipTeam;

View file

@ -0,0 +1,47 @@
import marcelImage from "../assets/marcel-scheibe.jpg";
const NewAboutHero = () => {
return (
<section className="min-h-screen flex items-center relative overflow-hidden">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-20 bg-[hsl(var(--hero-container))] rounded-[20px] w-[95%]">
<div className="grid lg:grid-cols-2 gap-16 items-center">
<div className="space-y-8">
<h1 className="text-5xl lg:text-6xl font-light text-foreground">
Über <span className="text-secondary">B2In</span>
</h1>
<blockquote className="text-xl lg:text-2xl text-muted-foreground italic leading-relaxed border-l-4 border-secondary pl-6">
"Unsere Vision ist es, Unternehmen durch innovative Konnektivitätslösungen zu verbinden
und nachhaltiges Wachstum in der digitalen Welt zu ermöglichen. Bei B2In schaffen wir
nicht nur Verbindungen wir bauen Brücken in die Zukunft."
</blockquote>
<div className="flex items-center gap-4">
<div className="w-16 h-px bg-secondary"></div>
<div>
<p className="font-semibold text-foreground">Marcel Scheibe</p>
<p className="text-sm text-muted-foreground">Gründer & CEO, B2In</p>
</div>
</div>
</div>
<div className="relative">
<div className="card-elevated rounded-3xl overflow-hidden">
<img
src={marcelImage}
alt="Marcel Scheibe, Gründer und CEO von B2In"
className="w-full h-96 lg:h-[500px] object-cover"
/>
</div>
<div className="absolute -bottom-6 -right-6 bg-secondary text-secondary-foreground p-6 rounded-2xl">
<div className="text-3xl font-bold">2019</div>
<p className="text-sm opacity-90">Gründungsjahr</p>
</div>
</div>
</div>
</div>
</section>
);
};
export default NewAboutHero;

View file

@ -0,0 +1,55 @@
const OurStory = () => {
return (
<section className="section-dark section-padding">
<div className="container-narrow text-center">
<h2 className="text-section-title text-[hsl(var(--dark-text))] mb-12">
Unsere <span className="text-secondary">Geschichte</span>
</h2>
<div className="grid md:grid-cols-3 gap-8 mb-16">
<div className="spacing-small">
<div className="w-12 h-12 mx-auto bg-secondary/20 rounded-full flex items-center justify-center">
<div className="w-6 h-6 bg-secondary rounded-full"></div>
</div>
<h3 className="text-xl font-semibold text-[hsl(var(--dark-text))]">Die Idee</h3>
<p className="text-dark-muted text-sm leading-relaxed">
2019 erkannten wir eine kritische Marktlücke: Unternehmen benötigten intelligente,
nachhaltige Konnektivitätslösungen für die digitale Transformation.
</p>
</div>
<div className="spacing-small">
<div className="w-12 h-12 mx-auto bg-secondary/20 rounded-full flex items-center justify-center">
<div className="w-6 h-6 bg-secondary rounded-full"></div>
</div>
<h3 className="text-xl font-semibold text-[hsl(var(--dark-text))]">Die Mission</h3>
<p className="text-dark-muted text-sm leading-relaxed">
Wir entwickelten innovative B2B-Lösungen, die Unternehmen dabei unterstützen,
effizienter zu arbeiten und nachhaltiges Wachstum zu erzielen.
</p>
</div>
<div className="spacing-small">
<div className="w-12 h-12 mx-auto bg-secondary/20 rounded-full flex items-center justify-center">
<div className="w-6 h-6 bg-secondary rounded-full"></div>
</div>
<h3 className="text-xl font-semibold text-[hsl(var(--dark-text))]">Die Zukunft</h3>
<p className="text-dark-muted text-sm leading-relaxed">
Heute sind wir stolz darauf, hunderte Unternehmen dabei zu unterstützen,
ihre digitalen Ziele zu erreichen und neue Märkte zu erschließen.
</p>
</div>
</div>
<p className="text-large text-dark-muted leading-relaxed max-w-3xl mx-auto">
Was als Vision begann, traditionelle Geschäftsprozesse zu revolutionieren, ist heute eine
bewährte Plattform für digitale Innovation. B2In schließt die Lücke zwischen
traditionellen Unternehmen und modernen, digitalen Lösungen durch maßgeschneiderte
Konnektivitätsservices, die Effizienz steigern und nachhaltiges Wachstum fördern.
</p>
</div>
</section>
);
};
export default OurStory;

View file

@ -0,0 +1,71 @@
const OurValues = () => {
const values = [
{
title: "Innovation",
description: "Wir entwickeln kontinuierlich neue Lösungen, die unseren Kunden einen Wettbewerbsvorteil verschaffen.",
icon: "💡"
},
{
title: "Konnektivität",
description: "Wir verbinden Unternehmen, Technologien und Menschen für optimale Geschäftsergebnisse.",
icon: "🔗"
},
{
title: "Qualität",
description: "Höchste Standards in allen Bereichen - von der Produktentwicklung bis zum Kundenservice.",
icon: "⭐"
},
{
title: "Vertrauen",
description: "Transparente Kommunikation und zuverlässige Partnerschaften bilden das Fundament unserer Arbeit.",
icon: "🤝"
},
{
title: "Nachhaltigkeit",
description: "Verantwortungsvolle Geschäftspraktiken für langfristigen Erfolg und positive gesellschaftliche Wirkung.",
icon: "🌱"
}
];
return (
<section className="py-20 px-4">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<h2 className="text-4xl lg:text-5xl font-light text-foreground mb-6">
Unsere <span className="text-secondary">Werte</span>
</h2>
<p className="text-muted-foreground text-lg max-w-2xl mx-auto">
Diese fünf Grundpfeiler leiten unser tägliches Handeln und definieren,
wer wir als Unternehmen sind und wofür wir stehen.
</p>
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{values.map((value, index) => (
<div key={index} className={`group ${index === 4 ? 'md:col-span-2 lg:col-span-1 lg:col-start-2' : ''}`}>
<div className="card-elevated p-8 rounded-3xl h-full hover-scale transition-all duration-300">
<div className="text-center space-y-6">
<div className="text-5xl mb-4">{value.icon}</div>
<h3 className="text-2xl font-semibold text-foreground">
{value.title}
</h3>
<div className="w-12 h-px bg-secondary mx-auto"></div>
<p className="text-muted-foreground leading-relaxed">
{value.description}
</p>
</div>
<div className="absolute bottom-0 left-0 right-0 h-1 bg-gradient-to-r from-secondary/20 via-secondary to-secondary/20 transform scale-x-0 group-hover:scale-x-100 transition-transform duration-300"></div>
</div>
</div>
))}
</div>
</div>
</section>
);
};
export default OurValues;

View file

@ -0,0 +1,151 @@
import { TrendingUp, Globe, Handshake, Award, Target, Settings } from "lucide-react";
import accommodationImage from "../assets/accommodation-1.jpg";
const PartnerBenefits = () => {
const brokerBenefits = [
{
icon: TrendingUp,
title: "Lifetime-Provisionsmodell",
description: "Profitieren Sie von kontinuierlichen Einnahmen durch unser innovatives Vergütungssystem"
},
{
icon: Target,
title: "Schnellere Vermarktung",
description: "Durchdachte Wohnkonzepte verkürzen Vermarktungszeiten und erhöhen Ihre Erfolgsquote"
},
{
icon: Award,
title: "Mehrwert für Ihre Kunden",
description: "Bieten Sie Ihren Kunden exklusive, kuratierte Immobilienerlebnisse"
}
];
const supplierBenefits = [
{
icon: Globe,
title: "Zugang zu internationalen Märkten",
description: "Erweitern Sie Ihre Reichweite über Grenzen hinweg mit unserem globalen Netzwerk"
},
{
icon: Handshake,
title: "Faire Konditionen",
description: "Transparente und partnerschaftliche Geschäftsbedingungen für nachhaltigen Erfolg"
},
{
icon: Settings,
title: "Einfache Produktverwaltung",
description: "Intuitive Plattform für die Verwaltung und Präsentation Ihrer Produkte"
}
];
return (
<section className="py-16 px-4">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<h2 className="text-section-title text-foreground mb-6">
Warum Partner werden?
</h2>
<p className="text-muted-foreground text-lg max-w-3xl mx-auto">
Entdecken Sie die Vorteile einer Partnerschaft mit B2In und
wie Sie von unserem innovativen Ecosystem profitieren können.
</p>
</div>
<div className="grid lg:grid-cols-2 gap-16">
{/* Für Makler */}
<div className="space-y-8">
<div className="text-center lg:text-left">
<div className="inline-flex items-center gap-2 bg-primary/10 text-primary px-4 py-2 rounded-full text-sm font-medium mb-6">
<TrendingUp className="w-4 h-4" />
Für Makler
</div>
<h3 className="text-3xl font-light text-foreground mb-6">
Revolutionäres <span className="text-primary">Provisionsmodell</span>
</h3>
</div>
<div className="space-y-6">
{brokerBenefits.map((benefit, index) => (
<div key={index} className="card-elevated p-6 rounded-xl">
<div className="flex gap-4">
<div className="flex-shrink-0 w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center">
<benefit.icon className="w-6 h-6 text-primary" />
</div>
<div>
<h4 className="text-lg font-semibold text-foreground mb-2">
{benefit.title}
</h4>
<p className="text-muted-foreground">
{benefit.description}
</p>
</div>
</div>
</div>
))}
</div>
<div className="bg-primary/5 rounded-xl p-6">
<div className="text-center">
<div className="text-3xl font-bold text-primary mb-2">3.5% - </div>
<p className="text-sm text-muted-foreground">
Erstprovision bis Lifetime-Vergütung
</p>
</div>
</div>
</div>
{/* Für Lieferanten */}
<div className="space-y-8">
<div className="text-center lg:text-left">
<div className="inline-flex items-center gap-2 bg-accent/50 text-accent-foreground px-4 py-2 rounded-full text-sm font-medium mb-6">
<Globe className="w-4 h-4" />
Für Lieferanten
</div>
<h3 className="text-3xl font-light text-foreground mb-6">
Globale <span className="text-primary">Marktchancen</span>
</h3>
</div>
<div className="space-y-6">
{supplierBenefits.map((benefit, index) => (
<div key={index} className="card-elevated p-6 rounded-xl">
<div className="flex gap-4">
<div className="flex-shrink-0 w-12 h-12 rounded-xl bg-accent/20 flex items-center justify-center">
<benefit.icon className="w-6 h-6 text-accent-foreground" />
</div>
<div>
<h4 className="text-lg font-semibold text-foreground mb-2">
{benefit.title}
</h4>
<p className="text-muted-foreground">
{benefit.description}
</p>
</div>
</div>
</div>
))}
</div>
<div className="relative">
<div className="card-elevated p-0 overflow-hidden rounded-xl">
<img
src={accommodationImage}
alt="Partner success visualization"
className="w-full h-48 object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent flex items-end">
<div className="p-6 text-white">
<div className="text-2xl font-bold mb-1">500+</div>
<p className="text-sm opacity-90">Erfolgreiche Partner</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
};
export default PartnerBenefits;

View file

@ -0,0 +1,53 @@
import { Button } from "./ui/button";
import { Link } from "react-router-dom";
const PartnerCTA = () => {
return (
<section className="section-dark section-padding">
<div className="container-narrow text-center">
<div className="spacing-section">
<h2 className="text-5xl lg:text-6xl font-light text-[hsl(var(--dark-text))] leading-tight">
Wachsen Sie <br />
<span className="text-secondary">mit uns</span>
</h2>
<div className="w-16 h-px bg-secondary mx-auto"></div>
<p className="text-large text-dark-muted leading-relaxed max-w-2xl mx-auto">
Werden Sie Teil des B2In-Partnernetzwerks und erschließen Sie neue
Geschäftsmöglichkeiten durch innovative Konnektivitätslösungen.
</p>
<div className="grid md:grid-cols-3 gap-8 py-8">
<div className="text-center space-y-3">
<div className="text-4xl font-light text-secondary">500+</div>
<p className="text-dark-muted text-sm">Aktive Partner</p>
</div>
<div className="text-center space-y-3">
<div className="text-4xl font-light text-secondary">98%</div>
<p className="text-dark-muted text-sm">Zufriedenheitsrate</p>
</div>
<div className="text-center space-y-3">
<div className="text-4xl font-light text-secondary">24/7</div>
<p className="text-dark-muted text-sm">Partner-Support</p>
</div>
</div>
<div className="spacing-content">
<Link to="/contact">
<button className="btn-accent px-12 py-6 rounded-2xl text-lg">
Werden Sie B2In Partner
</button>
</Link>
<p className="text-dark-muted text-sm">
Entdecken Sie die Vorteile einer strategischen Partnerschaft mit B2In
</p>
</div>
</div>
</div>
</section>
);
};
export default PartnerCTA;

View file

@ -0,0 +1,118 @@
import { TrendingUp, Globe, Handshake, Award, Target } from "lucide-react";
const PartnerHero = () => {
return (
<section className="min-h-screen flex items-center relative overflow-hidden">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-20 bg-[hsl(var(--hero-container))] rounded-[20px] w-[95%]">
<div className="grid lg:grid-cols-2 gap-16 items-center">
<div className="space-y-8">
<h1 className="text-5xl lg:text-7xl font-light text-foreground">
Wachsen Sie mit uns.<br />
Werden Sie <span className="text-primary">B2In Partner</span>.
</h1>
<p className="text-xl lg:text-2xl text-muted-foreground leading-relaxed">
Werden Sie Teil des B2In Ecosystems und profitieren Sie von innovativen
Geschäftsmodellen, die nachhaltiges Wachstum und langfristigen Erfolg ermöglichen.
Gemeinsam gestalten wir die Zukunft der Immobilienbranche.
</p>
<div className="grid grid-cols-2 gap-6">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center">
<TrendingUp className="w-6 h-6 text-primary" />
</div>
<div>
<p className="font-semibold text-foreground">Makler</p>
<p className="text-sm text-muted-foreground">Lifetime-Vergütung</p>
</div>
</div>
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center">
<Globe className="w-6 h-6 text-primary" />
</div>
<div>
<p className="font-semibold text-foreground">Lieferanten</p>
<p className="text-sm text-muted-foreground">Globale Märkte</p>
</div>
</div>
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center">
<Handshake className="w-6 h-6 text-primary" />
</div>
<div>
<p className="font-semibold text-foreground">Partnership</p>
<p className="text-sm text-muted-foreground">Faire Konditionen</p>
</div>
</div>
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center">
<Award className="w-6 h-6 text-primary" />
</div>
<div>
<p className="font-semibold text-foreground">Erfolg</p>
<p className="text-sm text-muted-foreground">Nachhaltiges Wachstum</p>
</div>
</div>
</div>
</div>
<div className="relative">
<div className="card-elevated rounded-3xl p-12 bg-background/80 backdrop-blur-sm">
<div className="space-y-8">
{/* Central Partnership Hub */}
<div className="text-center">
<div className="w-24 h-24 mx-auto rounded-full bg-primary flex items-center justify-center mb-4">
<Handshake className="w-12 h-12 text-white" />
</div>
<h3 className="text-xl font-semibold text-foreground">Partner Network</h3>
<p className="text-sm text-muted-foreground">Werden Sie Teil unseres Ecosystems</p>
</div>
{/* Partner Types */}
<div className="grid grid-cols-2 gap-8">
<div className="text-center">
<div className="w-16 h-16 mx-auto rounded-full bg-accent flex items-center justify-center mb-3">
<TrendingUp className="w-8 h-8 text-accent-foreground" />
</div>
<p className="font-medium text-sm">Makler</p>
<p className="text-xs text-muted-foreground">Lifetime-Modell</p>
</div>
<div className="text-center">
<div className="w-16 h-16 mx-auto rounded-full bg-accent flex items-center justify-center mb-3">
<Globe className="w-8 h-8 text-accent-foreground" />
</div>
<p className="font-medium text-sm">Lieferanten</p>
<p className="text-xs text-muted-foreground">Global Markets</p>
</div>
<div className="text-center">
<div className="w-16 h-16 mx-auto rounded-full bg-accent flex items-center justify-center mb-3">
<Target className="w-8 h-8 text-accent-foreground" />
</div>
<p className="font-medium text-sm">Erfolg</p>
<p className="text-xs text-muted-foreground">Messbare Ziele</p>
</div>
<div className="text-center">
<div className="w-16 h-16 mx-auto rounded-full bg-accent flex items-center justify-center mb-3">
<Award className="w-8 h-8 text-accent-foreground" />
</div>
<p className="font-medium text-sm">Qualität</p>
<p className="text-xs text-muted-foreground">Premium Standards</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
};
export default PartnerHero;

View file

@ -0,0 +1,110 @@
import { FileText, Search, Rocket } from "lucide-react";
import { Link } from "react-router-dom";
import room1Image from "../assets/room-1.jpg";
import room2Image from "../assets/room-2.jpg";
import room3Image from "../assets/room-3.jpg";
const PartnerProcess = () => {
const steps = [
{
step: "1",
title: "Bewerben",
description: "Senden Sie uns Ihre Bewerbungsunterlagen und stellen Sie sich sowie Ihr Unternehmen vor. Wir prüfen Ihre Qualifikationen.",
icon: FileText,
image: room1Image
},
{
step: "2",
title: "Prüfung",
description: "Unser Expertenteam überprüft Ihre Bewerbung sorgfältig. Bei positivem Ergebnis laden wir Sie zu einem persönlichen Gespräch ein.",
icon: Search,
image: room2Image
},
{
step: "3",
title: "Onboarding",
description: "Nach erfolgreicher Aufnahme erhalten Sie Zugang zu unserer Plattform und umfassende Schulungen für einen erfolgreichen Start.",
icon: Rocket,
image: room3Image
}
];
return (
<section className="py-16 px-4 bg-accent">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<h2 className="text-section-title text-foreground mb-6">
So werden Sie <span className="text-primary">Partner</span>
</h2>
<p className="text-muted-foreground text-lg max-w-3xl mx-auto">
In nur drei einfachen Schritten werden Sie Teil des B2In Ecosystems
und können von allen Vorteilen unserer Partnerschaft profitieren.
</p>
</div>
<div className="grid md:grid-cols-3 gap-8 mb-16">
{steps.map((step, index) => (
<div key={index} className="card-elevated p-0 overflow-hidden group hover:shadow-[var(--shadow-elevated)] transition-all duration-300">
<div className="relative overflow-hidden">
<img
src={step.image}
alt={step.title}
className="w-full h-64 object-cover group-hover:scale-105 transition-transform duration-300"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
{/* Step Number Badge */}
<div className="absolute top-4 left-4 w-12 h-12 rounded-full bg-primary text-white flex items-center justify-center font-bold text-lg">
{step.step}
</div>
</div>
<div className="p-8">
<div className="flex items-center gap-3 mb-4">
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center">
<step.icon className="w-5 h-5 text-primary" />
</div>
<h3 className="text-2xl font-medium text-foreground">
{step.title}
</h3>
</div>
<p className="text-muted-foreground leading-relaxed mb-6">
{step.description}
</p>
{index === steps.length - 1 && (
<Link to="/contact">
<button className="btn-secondary w-full">
Jetzt starten
</button>
</Link>
)}
</div>
</div>
))}
</div>
{/* CTA Section */}
<div className="text-center">
<div className="card-elevated p-12 rounded-3xl bg-gradient-to-br from-primary/10 to-primary/5">
<h3 className="text-3xl font-light text-foreground mb-6">
Bereit für den nächsten <span className="text-primary">Schritt</span>?
</h3>
<p className="text-muted-foreground text-lg mb-8 max-w-2xl mx-auto">
Werden Sie noch heute Teil des B2In Ecosystems und profitieren Sie
von innovativen Geschäftsmodellen und nachhaltigen Erfolgsstrategien.
</p>
<Link to="/contact">
<button className="btn-primary text-lg px-8 py-4">
Jetzt Partner werden
</button>
</Link>
</div>
</div>
</div>
</section>
);
};
export default PartnerProcess;

View file

@ -0,0 +1,75 @@
import accommodationImage2 from "../assets/accommodation-2.jpg";
const PremierBargain = () => {
return (
<section className="section-dark py-20 px-4">
<div className="max-w-7xl mx-auto">
<div className="grid lg:grid-cols-2 gap-16 items-center">
<div className="space-y-8">
<div>
<h2 className="text-6xl font-light text-[hsl(var(--dark-foreground))] mb-2">
Premier
</h2>
<h2 className="text-6xl font-light text-wood-accent">
Bargain
</h2>
</div>
<div className="space-y-6">
<div className="border-l-4 border-wood-accent pl-6">
<h3 className="text-2xl font-medium text-[hsl(var(--dark-foreground))] mb-2">
Midtown Manhattan Retreat
</h3>
<p className="text-[hsl(var(--dark-muted))] mb-4">
Experience luxury in the heart of Manhattan with our exclusive premier suite featuring panoramic city views and world-class amenities.
</p>
<div className="flex items-center gap-4">
<span className="text-3xl font-light text-wood-accent">$200</span>
<span className="text-[hsl(var(--dark-muted))]">/night</span>
<span className="text-sm text-[hsl(var(--dark-muted))] line-through">$350/night</span>
</div>
</div>
<div className="grid grid-cols-2 gap-6">
<div className="space-y-2">
<div className="text-lg font-medium text-[hsl(var(--dark-foreground))]">4.9/5</div>
<p className="text-[hsl(var(--dark-muted))] text-sm">Guest Rating</p>
</div>
<div className="space-y-2">
<div className="text-lg font-medium text-[hsl(var(--dark-foreground))]">1,200+</div>
<p className="text-[hsl(var(--dark-muted))] text-sm">Reviews</p>
</div>
</div>
<button className="btn-secondary bg-wood-accent text-primary hover:bg-wood-accent/90">
Book Premier Deal
</button>
</div>
</div>
<div className="relative">
<div className="card-elevated bg-[hsl(var(--dark-muted))] p-0 overflow-hidden rounded-3xl">
<img
src={accommodationImage2}
alt="Premier Manhattan suite interior"
className="w-full h-96 object-cover"
/>
<div className="absolute top-6 right-6 bg-wood-accent text-primary px-4 py-2 rounded-lg">
<span className="text-sm font-medium">43% OFF</span>
</div>
</div>
<div className="absolute -bottom-4 -left-4 bg-white/10 backdrop-blur-sm border border-white/20 rounded-xl p-4">
<div className="flex items-center gap-3">
<div className="w-3 h-3 bg-green-500 rounded-full animate-pulse"></div>
<span className="text-white text-sm">2 rooms left at this price</span>
</div>
</div>
</div>
</div>
</div>
</section>
);
};
export default PremierBargain;

View file

@ -0,0 +1,80 @@
import { ChevronDown } from "lucide-react";
import { useState } from "react";
const QueriesSection = () => {
const [openFaq, setOpenFaq] = useState(0);
const faqs = [
{
question: "What is included in the booking?",
answer: "All bookings include room accommodation, complimentary WiFi, 24/7 concierge service, and access to hotel facilities. Breakfast and additional services may vary by property."
},
{
question: "Can I cancel or modify my reservation?",
answer: "Most reservations can be cancelled or modified up to 24 hours before check-in without penalty. Please check the specific cancellation policy for your selected property."
},
{
question: "Do you offer group booking discounts?",
answer: "Yes, we offer special rates for group bookings of 5 or more rooms. Contact our group sales team for customized packages and exclusive rates."
},
{
question: "What payment methods do you accept?",
answer: "We accept all major credit cards (Visa, Mastercard, American Express), PayPal, and bank transfers. Payment is secure and encrypted for your protection."
}
];
return (
<section className="py-16 lg:py-24">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
{/* Section Title */}
<div className="text-center mb-16">
<h2 className="text-section-title">Common Queries</h2>
<p className="text-muted-foreground text-lg mt-4">
Find answers to frequently asked questions about our services
</p>
</div>
{/* FAQ List */}
<div className="space-y-4">
{faqs.map((faq, index) => (
<div key={index} className="card-elevated">
<button
onClick={() => setOpenFaq(openFaq === index ? -1 : index)}
className="w-full p-6 text-left flex items-center justify-between hover:bg-muted/20 transition-colors"
>
<h3 className="text-lg font-medium pr-4">{faq.question}</h3>
<ChevronDown
className={`w-5 h-5 text-muted-foreground transition-transform duration-200 ${
openFaq === index ? 'rotate-180' : ''
}`}
/>
</button>
<div className={`overflow-hidden transition-all duration-300 ${
openFaq === index ? 'max-h-96 pb-6' : 'max-h-0'
}`}>
<div className="px-6">
<p className="text-muted-foreground leading-relaxed">
{faq.answer}
</p>
</div>
</div>
</div>
))}
</div>
{/* Contact CTA */}
<div className="text-center mt-12">
<p className="text-muted-foreground mb-4">
Still have questions? We're here to help.
</p>
<button className="btn-secondary">
Contact Support
</button>
</div>
</div>
</section>
);
};
export default QueriesSection;

View file

@ -0,0 +1,67 @@
import { Heart } from "lucide-react";
import room1 from "../assets/room-1.jpg";
import room2 from "../assets/room-2.jpg";
import room3 from "../assets/room-3.jpg";
const RoomGallery = () => {
const rooms = [
{
id: 1,
image: room1,
title: "Deluxe Suite",
subtitle: "Premium comfort with city view",
price: "€450",
},
{
id: 2,
image: room2,
title: "Executive Room",
subtitle: "Modern luxury for business travelers",
price: "€350",
},
{
id: 3,
image: room3,
title: "Spa Suite",
subtitle: "Relaxation with wellness amenities",
price: "€520",
},
];
return (
<section className="py-16 lg:py-24">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid md:grid-cols-3 gap-8">
{rooms.map((room, index) => (
<div key={room.id} className="group cursor-pointer">
<div className="card-elevated overflow-hidden hover:shadow-[var(--shadow-elevated)] transition-all duration-500 group-hover:-translate-y-2">
<div className="relative overflow-hidden">
<img
src={room.image}
alt={room.title}
className="w-full h-64 object-cover group-hover:scale-105 transition-transform duration-700"
/>
<div className="absolute top-4 right-4">
<button className="w-10 h-10 bg-card/90 backdrop-blur-sm rounded-full flex items-center justify-center hover:bg-card transition-colors">
<Heart className="w-5 h-5 text-muted-foreground hover:text-red-500 transition-colors" />
</button>
</div>
<div className="absolute bottom-4 left-4 bg-card/95 backdrop-blur-sm rounded-lg px-3 py-1">
<span className="text-sm font-medium">{room.price}<span className="text-muted-foreground">/night</span></span>
</div>
</div>
<div className="p-6 space-y-2">
<h3 className="text-xl font-medium">{room.title}</h3>
<p className="text-muted-foreground">{room.subtitle}</p>
</div>
</div>
</div>
))}
</div>
</div>
</section>
);
};
export default RoomGallery;

View file

@ -0,0 +1,138 @@
import room1Image from "../assets/room-1.jpg";
import room2Image from "../assets/room-2.jpg";
import room3Image from "../assets/room-3.jpg";
import accommodationImage1 from "../assets/accommodation-1.jpg";
const RoomRecommendations = () => {
return (
<section className="py-20 px-4 bg-background">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<h2 className="text-6xl font-light text-foreground mb-4">recommend</h2>
<p className="text-muted-foreground text-lg">
Handpicked selections for your perfect stay
</p>
</div>
<div className="grid lg:grid-cols-3 gap-8">
<div className="lg:col-span-2 grid md:grid-cols-2 gap-6">
<div className="card-elevated p-0 overflow-hidden group">
<div className="relative">
<img
src={room1Image}
alt="Oceanview Deluxe Suite"
className="w-full h-48 object-cover group-hover:scale-105 transition-transform duration-300"
/>
<div className="absolute top-4 left-4 bg-white/10 backdrop-blur-sm border border-white/20 rounded-lg px-3 py-1">
<span className="text-white text-sm"></span>
</div>
</div>
<div className="p-6">
<h3 className="text-lg font-medium text-foreground mb-2">
Oceanview Deluxe Suite
</h3>
<p className="text-muted-foreground text-sm mb-4">
2-4 Guests Ocean View 45m²
</p>
<div className="flex justify-between items-center">
<span className="text-foreground font-medium">$420/night</span>
<button className="btn-secondary text-sm">Book Now</button>
</div>
</div>
</div>
<div className="card-elevated p-0 overflow-hidden group">
<div className="relative">
<img
src={room2Image}
alt="Garden View Villa"
className="w-full h-48 object-cover group-hover:scale-105 transition-transform duration-300"
/>
<div className="absolute top-4 left-4 bg-white/10 backdrop-blur-sm border border-white/20 rounded-lg px-3 py-1">
<span className="text-white text-sm"></span>
</div>
</div>
<div className="p-6">
<h3 className="text-lg font-medium text-foreground mb-2">
Garden View Villa
</h3>
<p className="text-muted-foreground text-sm mb-4">
4-6 Guests Garden View 65m²
</p>
<div className="flex justify-between items-center">
<span className="text-foreground font-medium">$520/night</span>
<button className="btn-secondary text-sm">Book Now</button>
</div>
</div>
</div>
<div className="card-elevated p-0 overflow-hidden group">
<div className="relative">
<img
src={room3Image}
alt="Executive Double Room"
className="w-full h-48 object-cover group-hover:scale-105 transition-transform duration-300"
/>
<div className="absolute top-4 left-4 bg-white/10 backdrop-blur-sm border border-white/20 rounded-lg px-3 py-1">
<span className="text-white text-sm"></span>
</div>
</div>
<div className="p-6">
<h3 className="text-lg font-medium text-foreground mb-2">
Executive Double Room
</h3>
<p className="text-muted-foreground text-sm mb-4">
2 Guests Business Class 35m²
</p>
<div className="flex justify-between items-center">
<span className="text-foreground font-medium">$290/night</span>
<button className="btn-secondary text-sm">Book Now</button>
</div>
</div>
</div>
<div className="card-elevated p-0 overflow-hidden group">
<div className="relative">
<img
src={accommodationImage1}
alt="Presidential Suite"
className="w-full h-48 object-cover group-hover:scale-105 transition-transform duration-300"
/>
<div className="absolute top-4 left-4 bg-white/10 backdrop-blur-sm border border-white/20 rounded-lg px-3 py-1">
<span className="text-white text-sm"></span>
</div>
</div>
<div className="p-6">
<h3 className="text-lg font-medium text-foreground mb-2">
Presidential Suite
</h3>
<p className="text-muted-foreground text-sm mb-4">
4-8 Guests Presidential 120m²
</p>
<div className="flex justify-between items-center">
<span className="text-foreground font-medium">$850/night</span>
<button className="btn-secondary text-sm">Book Now</button>
</div>
</div>
</div>
</div>
<div className="section-dark p-8 rounded-3xl flex flex-col justify-center items-center text-center">
<div className="text-8xl font-light text-wood-accent mb-4">20%</div>
<h3 className="text-2xl font-light text-[hsl(var(--dark-foreground))] mb-4">
Book accommodation and get 20% off on your first booking
</h3>
<p className="text-[hsl(var(--dark-muted))] text-sm mb-6">
Limited time offer - expires December 31
</p>
<button className="btn-secondary bg-wood-accent text-primary hover:bg-wood-accent/90">
Claim Offer
</button>
</div>
</div>
</div>
</section>
);
};
export default RoomRecommendations;

View file

@ -0,0 +1,106 @@
import room1Image from "../assets/room-1.jpg";
import room2Image from "../assets/room-2.jpg";
import room3Image from "../assets/room-3.jpg";
import accommodationImage1 from "../assets/accommodation-1.jpg";
import accommodationImage2 from "../assets/accommodation-2.jpg";
import heroRoomImage from "../assets/hero-room.jpg";
const RoomsGrid = () => {
const rooms = [
{
title: "Deluxe Suite - Ocean View",
category: "Ocean View",
image: room1Image,
price: "$420/night"
},
{
title: "Premium King Suite",
category: "Luxury Suite",
image: room2Image,
price: "$380/night"
},
{
title: "Executive Double Room",
category: "Business Class",
image: room3Image,
price: "$290/night"
},
{
title: "Royal Penthouse Suite",
category: "Presidential",
image: accommodationImage1,
price: "$650/night"
},
{
title: "Garden View Villa",
category: "Villa Collection",
image: accommodationImage2,
price: "$520/night"
},
{
title: "Classic Comfort Room",
category: "Standard Plus",
image: heroRoomImage,
price: "$220/night"
}
];
const categories = ["Ocean View", "Luxury Suite", "Business Class", "Family Suite", "Executive Suites", "Garden View Villa"];
return (
<section className="section-dark py-20 px-4">
<div className="max-w-7xl mx-auto">
<div className="flex flex-wrap justify-center gap-6 mb-12">
{categories.map((category, index) => (
<button
key={index}
className={`px-6 py-2 rounded-full text-sm transition-colors ${
index === 0
? 'bg-wood-accent text-primary'
: 'bg-transparent border border-[hsl(var(--dark-muted))] text-[hsl(var(--dark-foreground))] hover:bg-wood-accent hover:text-primary'
}`}
>
{category}
</button>
))}
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{rooms.map((room, index) => (
<div key={index} className="card-elevated bg-[hsl(var(--dark-muted))] p-0 overflow-hidden group hover:shadow-[var(--shadow-elevated)] transition-all duration-300">
<div className="relative overflow-hidden">
<img
src={room.image}
alt={room.title}
className="w-full h-64 object-cover group-hover:scale-105 transition-transform duration-300"
/>
<div className="absolute top-4 right-4 bg-white/10 backdrop-blur-sm border border-white/20 rounded-lg px-3 py-1">
<span className="text-white text-sm font-medium">{room.price}</span>
</div>
</div>
<div className="p-6">
<h3 className="text-xl font-medium text-[hsl(var(--dark-foreground))] mb-2">
{room.title}
</h3>
<p className="text-[hsl(var(--dark-muted))] text-sm mb-4">
{room.category}
</p>
<button className="btn-secondary bg-wood-accent text-primary hover:bg-wood-accent/90 w-full">
View Details
</button>
</div>
</div>
))}
</div>
<div className="text-center mt-12">
<button className="btn-secondary bg-wood-accent text-primary hover:bg-wood-accent/90">
View All Rooms
</button>
</div>
</div>
</section>
);
};
export default RoomsGrid;

View file

@ -0,0 +1,17 @@
const RoomsHero = () => {
return (
<section className="pt-24 pb-16 px-4 bg-background">
<div className="max-w-7xl mx-auto text-center">
<h1 className="text-8xl md:text-9xl font-light text-foreground mb-8 tracking-wide">
rooms
</h1>
<p className="text-muted-foreground text-lg max-w-2xl mx-auto">
Discover our carefully curated collection of premium accommodations,
each designed to provide the perfect blend of comfort and luxury.
</p>
</div>
</section>
);
};
export default RoomsHero;

View file

@ -0,0 +1,98 @@
import { Shield, Clock, Award, Users } from "lucide-react";
import accommodation1 from "../assets/accommodation-1.jpg";
const SecureSection = () => {
const features = [
{
icon: Shield,
title: "Secure Booking",
description: "Your payment and personal information is protected with industry-standard encryption."
},
{
icon: Clock,
title: "24/7 Support",
description: "Round-the-clock customer service to assist you anytime you need help."
},
{
icon: Award,
title: "Quality Assured",
description: "All properties are personally verified to meet our high standards."
},
{
icon: Users,
title: "Trusted by Thousands",
description: "Join over 50,000 satisfied guests who chose us for their stays."
}
];
return (
<section className="section-dark py-16 lg:py-24">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid lg:grid-cols-2 gap-12 lg:gap-16 items-center">
{/* Content */}
<div className="space-y-8">
<div className="space-y-6">
<h2 className="text-4xl lg:text-5xl font-light leading-tight">
secure &<br />
<span className="text-secondary">affordable</span>
</h2>
<p className="text-lg text-gray-300 leading-relaxed max-w-lg">
Experience peace of mind with our secure booking platform and competitive prices.
We guarantee the best rates for premium accommodations.
</p>
</div>
<div className="grid gap-6">
{features.map((feature, index) => (
<div key={index} className="flex items-start space-x-4">
<div className="flex-shrink-0 w-12 h-12 bg-secondary/20 rounded-lg flex items-center justify-center">
<feature.icon className="w-6 h-6 text-secondary" />
</div>
<div className="space-y-1">
<h3 className="font-medium text-white">{feature.title}</h3>
<p className="text-sm text-gray-300">{feature.description}</p>
</div>
</div>
))}
</div>
<button className="bg-secondary text-secondary-foreground hover:bg-secondary/90 px-8 py-3 rounded-lg font-medium transition-all duration-300 hover:shadow-lg">
Learn More
</button>
</div>
{/* Image */}
<div className="relative">
<div className="rounded-2xl overflow-hidden shadow-2xl">
<img
src={accommodation1}
alt="Secure and comfortable accommodation"
className="w-full h-[500px] object-cover"
/>
</div>
{/* Stats overlay */}
<div className="absolute bottom-6 left-6 right-6 bg-white/95 backdrop-blur-sm rounded-xl p-6">
<div className="grid grid-cols-3 gap-4 text-center">
<div>
<div className="text-2xl font-bold text-dark-bg">50K+</div>
<div className="text-sm text-gray-600">Happy Guests</div>
</div>
<div>
<div className="text-2xl font-bold text-dark-bg">500+</div>
<div className="text-sm text-gray-600">Properties</div>
</div>
<div>
<div className="text-2xl font-bold text-dark-bg">4.9</div>
<div className="text-sm text-gray-600">Rating</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
};
export default SecureSection;

View file

@ -0,0 +1,55 @@
import { MapPin, Star, Clock } from "lucide-react";
const SpotlightsSection = () => {
const spotlights = [
{
icon: MapPin,
title: "Prime Locations",
description: "Strategically located properties in the heart of major cities and tourist destinations worldwide."
},
{
icon: Star,
title: "Premium Service",
description: "Exceptional hospitality with personalized service tailored to exceed your expectations every time."
},
{
icon: Clock,
title: "Instant Booking",
description: "Book instantly with real-time availability and immediate confirmation for a seamless experience."
}
];
return (
<section className="py-16 lg:py-24 bg-muted/20">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
{/* Section Title */}
<div className="text-center mb-16">
<h2 className="text-section-title">spotlights</h2>
<p className="text-muted-foreground text-lg mt-4 max-w-2xl mx-auto">
Discover what makes our accommodation service stand out from the rest
</p>
</div>
{/* Spotlights Grid */}
<div className="grid md:grid-cols-3 gap-8 lg:gap-12">
{spotlights.map((spotlight, index) => (
<div key={index} className="text-center space-y-6 group">
<div className="mx-auto w-20 h-20 bg-secondary/10 rounded-2xl flex items-center justify-center group-hover:bg-secondary/20 transition-colors duration-300">
<spotlight.icon className="w-10 h-10 text-secondary" />
</div>
<div className="space-y-4">
<h3 className="text-xl font-medium">{spotlight.title}</h3>
<p className="text-muted-foreground leading-relaxed">
{spotlight.description}
</p>
</div>
</div>
))}
</div>
</div>
</section>
);
};
export default SpotlightsSection;

View file

@ -0,0 +1,35 @@
const StatsSection = () => {
const stats = [
{ number: "01", label: "Premium Locations", value: "500+ Hotels" },
{ number: "02", label: "Guest Satisfaction", value: "4.9 Rating" },
{ number: "03", label: "Years Experience", value: "15+ Years" },
{ number: "04", label: "Countries Served", value: "25+ Countries" },
{ number: "05", label: "Happy Customers", value: "50K+ Guests" },
];
return (
<section className="py-16 lg:py-24 bg-muted/30">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-2 md:grid-cols-5 gap-8">
{stats.map((stat, index) => (
<div key={index} className="text-center space-y-4">
<div className="text-4xl lg:text-6xl font-light text-secondary">
{stat.number}
</div>
<div className="space-y-2">
<div className="text-sm text-muted-foreground uppercase tracking-wide">
{stat.label}
</div>
<div className="text-lg font-medium">
{stat.value}
</div>
</div>
</div>
))}
</div>
</div>
</section>
);
};
export default StatsSection;

View file

@ -0,0 +1,123 @@
import { Store, Settings, CheckCircle, BarChart3 } from "lucide-react";
const SupplierSection = () => {
const benefits = [
{
icon: Store,
title: "Kuratierter Vertriebskanal",
description: "Zugang zu einer exklusiven, vorqualifizierten Kundenbasis mit hohem Qualitätsanspruch"
},
{
icon: Settings,
title: "Selbstverwaltung",
description: "Vollständige Kontrolle über Produktpräsentation, Preisgestaltung und Verfügbarkeit"
},
{
icon: CheckCircle,
title: "Zentrale Qualitätssicherung",
description: "Standardisierte Prozesse und Qualitätskontrollen für maximales Kundenvertrauen"
},
{
icon: BarChart3,
title: "Analytics & Insights",
description: "Detaillierte Verkaufsanalysen und Markteinblicke für optimierte Geschäftsentscheidungen"
}
];
return (
<section className="py-20 px-4 bg-muted/30">
<div className="max-w-7xl mx-auto">
<div className="grid lg:grid-cols-2 gap-16 items-center">
<div className="space-y-8">
<div>
<div className="inline-flex items-center gap-2 bg-primary/10 text-primary px-4 py-2 rounded-full text-sm font-medium mb-6">
<Store className="w-4 h-4" />
Für Lieferanten
</div>
<h2 className="text-4xl lg:text-5xl font-light text-foreground mb-6">
Premium <span className="text-primary">Vertriebsplattform</span>
</h2>
<p className="text-xl text-muted-foreground leading-relaxed">
Unsere Lieferanten-Plattform bietet Ihnen einen kuratierten
Vertriebskanal zu anspruchsvollen Kunden. Mit vollständiger
Selbstverwaltung und zentraler Qualitätssicherung maximieren
Sie Ihren Erfolg bei minimalen Aufwand.
</p>
</div>
<div className="space-y-6">
{benefits.map((benefit, index) => (
<div key={index} className="flex gap-4">
<div className="flex-shrink-0 w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center">
<benefit.icon className="w-6 h-6 text-primary" />
</div>
<div>
<h3 className="text-lg font-semibold text-foreground mb-2">
{benefit.title}
</h3>
<p className="text-muted-foreground">
{benefit.description}
</p>
</div>
</div>
))}
</div>
</div>
<div className="relative">
<div className="card-elevated rounded-3xl overflow-hidden">
<div className="p-8">
<h3 className="text-xl font-semibold text-foreground mb-6 text-center">
Lieferanten Dashboard
</h3>
<div className="space-y-4">
<div className="bg-accent/30 rounded-xl p-4">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium">Monatliche Verkäufe</span>
<span className="text-green-600 text-sm">+24%</span>
</div>
<div className="text-2xl font-bold text-foreground">89,420</div>
</div>
<div className="bg-accent/30 rounded-xl p-4">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium">Aktive Produkte</span>
<CheckCircle className="w-4 h-4 text-green-600" />
</div>
<div className="text-2xl font-bold text-foreground">247</div>
</div>
<div className="bg-primary/10 rounded-xl p-4">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium">Kundenbewertung</span>
<div className="flex gap-1">
{[1,2,3,4,5].map((star) => (
<div key={star} className="w-3 h-3 bg-primary rounded-full"></div>
))}
</div>
</div>
<div className="text-2xl font-bold text-primary">4.9/5.0</div>
</div>
<div className="grid grid-cols-2 gap-3">
<button className="bg-primary text-primary-foreground px-4 py-3 rounded-xl text-sm font-medium">
Produkt hinzufügen
</button>
<button className="bg-accent text-accent-foreground px-4 py-3 rounded-xl text-sm font-medium">
Analytics
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
};
export default SupplierSection;

View file

@ -0,0 +1,76 @@
const TestimonialsSection = () => {
const testimonials = [
{
name: "Sarah Johnson",
location: "New York, USA",
rating: 5,
text: "Absolutely incredible experience! The attention to detail and personalized service exceeded all my expectations. I'll definitely be returning.",
avatar: "SJ"
},
{
name: "Marcus Weber",
location: "Berlin, Germany",
rating: 5,
text: "The perfect blend of luxury and comfort. Every aspect of my stay was meticulously planned and executed. Highly recommended!",
avatar: "MW"
},
{
name: "Emily Chen",
location: "Tokyo, Japan",
rating: 5,
text: "From check-in to check-out, everything was seamless. The staff went above and beyond to make my business trip comfortable and productive.",
avatar: "EC"
}
];
return (
<section className="py-20 px-4 bg-background">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<h2 className="text-6xl font-light text-foreground mb-4">testimonials</h2>
<p className="text-muted-foreground text-lg">
What our guests say about their experience
</p>
</div>
<div className="grid md:grid-cols-3 gap-8">
{testimonials.map((testimonial, index) => (
<div key={index} className="card-elevated p-8 text-center">
<div className="w-16 h-16 bg-wood-accent rounded-full flex items-center justify-center text-primary font-medium text-lg mb-6 mx-auto">
{testimonial.avatar}
</div>
<div className="flex justify-center gap-1 mb-4">
{[...Array(testimonial.rating)].map((_, i) => (
<span key={i} className="text-wood-accent text-lg"></span>
))}
</div>
<p className="text-muted-foreground leading-relaxed mb-6 italic">
"{testimonial.text}"
</p>
<div>
<h4 className="font-medium text-foreground">{testimonial.name}</h4>
<p className="text-muted-foreground text-sm">{testimonial.location}</p>
</div>
</div>
))}
</div>
<div className="text-center mt-12">
<div className="flex justify-center gap-2">
{[1, 2, 3, 4, 5].map((dot, index) => (
<div
key={index}
className={`w-2 h-2 rounded-full ${index === 0 ? 'bg-wood-accent' : 'bg-muted/30'}`}
/>
))}
</div>
</div>
</div>
</section>
);
};
export default TestimonialsSection;

View file

@ -0,0 +1,48 @@
import teamImage from "../assets/thomas-weber.jpg";
const VisionSection = () => {
return (
<section className="section-padding bg-muted/20">
<div className="container-padding">
<div className="grid lg:grid-cols-2 gap-12 lg:gap-16 items-center">
{/* Content */}
<div className="spacing-section">
<div className="spacing-content">
<h2 className="text-section-title">Gebaut auf Vertrauen</h2>
<div className="spacing-small text-large text-muted-foreground leading-relaxed">
<p>
Unsere Basis ist Vertrauen, angetrieben von Technologie und Innovation.
B2In ist nicht nur eine Holding, sondern ein aktiver Gestalter der Immobilienzukunft.
</p>
<p>
Wir vereinfachen komplexe Prozesse durch eine zentrale digitale Plattform
das B2In-Portal und schaffen dabei Transparenz, Qualität und Innovation
in jedem Schritt unserer Zusammenarbeit.
</p>
<p>
Unser Engagement für Exzellenz zeigt sich in der Art, wie wir
Markenwerte leben und Partnerschaften aufbauen, die nachhaltigen
Erfolg für alle Beteiligten schaffen.
</p>
</div>
</div>
</div>
{/* Image */}
<div className="relative">
<div className="card-elevated rounded-3xl overflow-hidden">
<img
src={teamImage}
alt="Professionelles Team in kollaborativem Meeting"
className="w-full h-[500px] object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/20 to-transparent" />
</div>
</div>
</div>
</div>
</section>
);
};
export default VisionSection;

View file

@ -0,0 +1,52 @@
import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDown } from "lucide-react";
import { cn } from "@/lib/utils";
const Accordion = AccordionPrimitive.Root;
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item ref={ref} className={cn("border-b", className)} {...props} />
));
AccordionItem.displayName = "AccordionItem";
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
className,
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
));
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content>
));
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };

View file

@ -0,0 +1,104 @@
import * as React from "react";
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";
const AlertDialog = AlertDialogPrimitive.Root;
const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
const AlertDialogPortal = AlertDialogPrimitive.Portal;
const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className,
)}
{...props}
ref={ref}
/>
));
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
>(({ className, ...props }, ref) => (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className,
)}
{...props}
/>
</AlertDialogPortal>
));
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
);
AlertDialogHeader.displayName = "AlertDialogHeader";
const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} />
);
AlertDialogFooter.displayName = "AlertDialogFooter";
const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title ref={ref} className={cn("text-lg font-semibold", className)} {...props} />
));
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
));
AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;
const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />
));
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel
ref={ref}
className={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
{...props}
/>
));
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
export {
AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
};

View file

@ -0,0 +1,43 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const alertVariants = cva(
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
{
variants: {
variant: {
default: "bg-background text-foreground",
destructive: "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
},
},
defaultVariants: {
variant: "default",
},
},
);
const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div ref={ref} role="alert" className={cn(alertVariants({ variant }), className)} {...props} />
));
Alert.displayName = "Alert";
const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h5 ref={ref} className={cn("mb-1 font-medium leading-none tracking-tight", className)} {...props} />
),
);
AlertTitle.displayName = "AlertTitle";
const AlertDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn("text-sm [&_p]:leading-relaxed", className)} {...props} />
),
);
AlertDescription.displayName = "AlertDescription";
export { Alert, AlertTitle, AlertDescription };

View file

@ -0,0 +1,5 @@
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
const AspectRatio = AspectRatioPrimitive.Root;
export { AspectRatio };

View file

@ -0,0 +1,38 @@
import * as React from "react";
import * as AvatarPrimitive from "@radix-ui/react-avatar";
import { cn } from "@/lib/utils";
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn("relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className)}
{...props}
/>
));
Avatar.displayName = AvatarPrimitive.Root.displayName;
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image ref={ref} className={cn("aspect-square h-full w-full", className)} {...props} />
));
AvatarImage.displayName = AvatarPrimitive.Image.displayName;
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn("flex h-full w-full items-center justify-center rounded-full bg-muted", className)}
{...props}
/>
));
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
export { Avatar, AvatarImage, AvatarFallback };

View file

@ -0,0 +1,29 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
},
);
export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
}
export { Badge, badgeVariants };

View file

@ -0,0 +1,90 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { ChevronRight, MoreHorizontal } from "lucide-react";
import { cn } from "@/lib/utils";
const Breadcrumb = React.forwardRef<
HTMLElement,
React.ComponentPropsWithoutRef<"nav"> & {
separator?: React.ReactNode;
}
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />);
Breadcrumb.displayName = "Breadcrumb";
const BreadcrumbList = React.forwardRef<HTMLOListElement, React.ComponentPropsWithoutRef<"ol">>(
({ className, ...props }, ref) => (
<ol
ref={ref}
className={cn(
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
className,
)}
{...props}
/>
),
);
BreadcrumbList.displayName = "BreadcrumbList";
const BreadcrumbItem = React.forwardRef<HTMLLIElement, React.ComponentPropsWithoutRef<"li">>(
({ className, ...props }, ref) => (
<li ref={ref} className={cn("inline-flex items-center gap-1.5", className)} {...props} />
),
);
BreadcrumbItem.displayName = "BreadcrumbItem";
const BreadcrumbLink = React.forwardRef<
HTMLAnchorElement,
React.ComponentPropsWithoutRef<"a"> & {
asChild?: boolean;
}
>(({ asChild, className, ...props }, ref) => {
const Comp = asChild ? Slot : "a";
return <Comp ref={ref} className={cn("transition-colors hover:text-foreground", className)} {...props} />;
});
BreadcrumbLink.displayName = "BreadcrumbLink";
const BreadcrumbPage = React.forwardRef<HTMLSpanElement, React.ComponentPropsWithoutRef<"span">>(
({ className, ...props }, ref) => (
<span
ref={ref}
role="link"
aria-disabled="true"
aria-current="page"
className={cn("font-normal text-foreground", className)}
{...props}
/>
),
);
BreadcrumbPage.displayName = "BreadcrumbPage";
const BreadcrumbSeparator = ({ children, className, ...props }: React.ComponentProps<"li">) => (
<li role="presentation" aria-hidden="true" className={cn("[&>svg]:size-3.5", className)} {...props}>
{children ?? <ChevronRight />}
</li>
);
BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
const BreadcrumbEllipsis = ({ className, ...props }: React.ComponentProps<"span">) => (
<span
role="presentation"
aria-hidden="true"
className={cn("flex h-9 w-9 items-center justify-center", className)}
{...props}
>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More</span>
</span>
);
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis";
export {
Breadcrumb,
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
BreadcrumbEllipsis,
};

View file

@ -0,0 +1,47 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
},
);
Button.displayName = "Button";
export { Button, buttonVariants };

View file

@ -0,0 +1,54 @@
import * as React from "react";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { DayPicker } from "react-day-picker";
import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) {
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn("p-3", className)}
classNames={{
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
month: "space-y-4",
caption: "flex justify-center pt-1 relative items-center",
caption_label: "text-sm font-medium",
nav: "space-x-1 flex items-center",
nav_button: cn(
buttonVariants({ variant: "outline" }),
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100",
),
nav_button_previous: "absolute left-1",
nav_button_next: "absolute right-1",
table: "w-full border-collapse space-y-1",
head_row: "flex",
head_cell: "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
row: "flex w-full mt-2",
cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
day: cn(buttonVariants({ variant: "ghost" }), "h-9 w-9 p-0 font-normal aria-selected:opacity-100"),
day_range_end: "day-range-end",
day_selected:
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
day_today: "bg-accent text-accent-foreground",
day_outside:
"day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
day_disabled: "text-muted-foreground opacity-50",
day_range_middle: "aria-selected:bg-accent aria-selected:text-accent-foreground",
day_hidden: "invisible",
...classNames,
}}
components={{
IconLeft: ({ ..._props }) => <ChevronLeft className="h-4 w-4" />,
IconRight: ({ ..._props }) => <ChevronRight className="h-4 w-4" />,
}}
{...props}
/>
);
}
Calendar.displayName = "Calendar";
export { Calendar };

View file

@ -0,0 +1,43 @@
import * as React from "react";
import { cn } from "@/lib/utils";
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)} {...props} />
));
Card.displayName = "Card";
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
),
);
CardHeader.displayName = "CardHeader";
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h3 ref={ref} className={cn("text-2xl font-semibold leading-none tracking-tight", className)} {...props} />
),
);
CardTitle.displayName = "CardTitle";
const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, ...props }, ref) => (
<p ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
),
);
CardDescription.displayName = "CardDescription";
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />,
);
CardContent.displayName = "CardContent";
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex items-center p-6 pt-0", className)} {...props} />
),
);
CardFooter.displayName = "CardFooter";
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };

View file

@ -0,0 +1,224 @@
import * as React from "react";
import useEmblaCarousel, { type UseEmblaCarouselType } from "embla-carousel-react";
import { ArrowLeft, ArrowRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
type CarouselApi = UseEmblaCarouselType[1];
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
type CarouselOptions = UseCarouselParameters[0];
type CarouselPlugin = UseCarouselParameters[1];
type CarouselProps = {
opts?: CarouselOptions;
plugins?: CarouselPlugin;
orientation?: "horizontal" | "vertical";
setApi?: (api: CarouselApi) => void;
};
type CarouselContextProps = {
carouselRef: ReturnType<typeof useEmblaCarousel>[0];
api: ReturnType<typeof useEmblaCarousel>[1];
scrollPrev: () => void;
scrollNext: () => void;
canScrollPrev: boolean;
canScrollNext: boolean;
} & CarouselProps;
const CarouselContext = React.createContext<CarouselContextProps | null>(null);
function useCarousel() {
const context = React.useContext(CarouselContext);
if (!context) {
throw new Error("useCarousel must be used within a <Carousel />");
}
return context;
}
const Carousel = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement> & CarouselProps>(
({ orientation = "horizontal", opts, setApi, plugins, className, children, ...props }, ref) => {
const [carouselRef, api] = useEmblaCarousel(
{
...opts,
axis: orientation === "horizontal" ? "x" : "y",
},
plugins,
);
const [canScrollPrev, setCanScrollPrev] = React.useState(false);
const [canScrollNext, setCanScrollNext] = React.useState(false);
const onSelect = React.useCallback((api: CarouselApi) => {
if (!api) {
return;
}
setCanScrollPrev(api.canScrollPrev());
setCanScrollNext(api.canScrollNext());
}, []);
const scrollPrev = React.useCallback(() => {
api?.scrollPrev();
}, [api]);
const scrollNext = React.useCallback(() => {
api?.scrollNext();
}, [api]);
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "ArrowLeft") {
event.preventDefault();
scrollPrev();
} else if (event.key === "ArrowRight") {
event.preventDefault();
scrollNext();
}
},
[scrollPrev, scrollNext],
);
React.useEffect(() => {
if (!api || !setApi) {
return;
}
setApi(api);
}, [api, setApi]);
React.useEffect(() => {
if (!api) {
return;
}
onSelect(api);
api.on("reInit", onSelect);
api.on("select", onSelect);
return () => {
api?.off("select", onSelect);
};
}, [api, onSelect]);
return (
<CarouselContext.Provider
value={{
carouselRef,
api: api,
opts,
orientation: orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
scrollPrev,
scrollNext,
canScrollPrev,
canScrollNext,
}}
>
<div
ref={ref}
onKeyDownCapture={handleKeyDown}
className={cn("relative", className)}
role="region"
aria-roledescription="carousel"
{...props}
>
{children}
</div>
</CarouselContext.Provider>
);
},
);
Carousel.displayName = "Carousel";
const CarouselContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => {
const { carouselRef, orientation } = useCarousel();
return (
<div ref={carouselRef} className="overflow-hidden">
<div
ref={ref}
className={cn("flex", orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col", className)}
{...props}
/>
</div>
);
},
);
CarouselContent.displayName = "CarouselContent";
const CarouselItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => {
const { orientation } = useCarousel();
return (
<div
ref={ref}
role="group"
aria-roledescription="slide"
className={cn("min-w-0 shrink-0 grow-0 basis-full", orientation === "horizontal" ? "pl-4" : "pt-4", className)}
{...props}
/>
);
},
);
CarouselItem.displayName = "CarouselItem";
const CarouselPrevious = React.forwardRef<HTMLButtonElement, React.ComponentProps<typeof Button>>(
({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollPrev, canScrollPrev } = useCarousel();
return (
<Button
ref={ref}
variant={variant}
size={size}
className={cn(
"absolute h-8 w-8 rounded-full",
orientation === "horizontal"
? "-left-12 top-1/2 -translate-y-1/2"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
className,
)}
disabled={!canScrollPrev}
onClick={scrollPrev}
{...props}
>
<ArrowLeft className="h-4 w-4" />
<span className="sr-only">Previous slide</span>
</Button>
);
},
);
CarouselPrevious.displayName = "CarouselPrevious";
const CarouselNext = React.forwardRef<HTMLButtonElement, React.ComponentProps<typeof Button>>(
({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollNext, canScrollNext } = useCarousel();
return (
<Button
ref={ref}
variant={variant}
size={size}
className={cn(
"absolute h-8 w-8 rounded-full",
orientation === "horizontal"
? "-right-12 top-1/2 -translate-y-1/2"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
className,
)}
disabled={!canScrollNext}
onClick={scrollNext}
{...props}
>
<ArrowRight className="h-4 w-4" />
<span className="sr-only">Next slide</span>
</Button>
);
},
);
CarouselNext.displayName = "CarouselNext";
export { type CarouselApi, Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext };

View file

@ -0,0 +1,303 @@
import * as React from "react";
import * as RechartsPrimitive from "recharts";
import { cn } from "@/lib/utils";
// Format: { THEME_NAME: CSS_SELECTOR }
const THEMES = { light: "", dark: ".dark" } as const;
export type ChartConfig = {
[k in string]: {
label?: React.ReactNode;
icon?: React.ComponentType;
} & ({ color?: string; theme?: never } | { color?: never; theme: Record<keyof typeof THEMES, string> });
};
type ChartContextProps = {
config: ChartConfig;
};
const ChartContext = React.createContext<ChartContextProps | null>(null);
function useChart() {
const context = React.useContext(ChartContext);
if (!context) {
throw new Error("useChart must be used within a <ChartContainer />");
}
return context;
}
const ChartContainer = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & {
config: ChartConfig;
children: React.ComponentProps<typeof RechartsPrimitive.ResponsiveContainer>["children"];
}
>(({ id, className, children, config, ...props }, ref) => {
const uniqueId = React.useId();
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
return (
<ChartContext.Provider value={{ config }}>
<div
data-chart={chartId}
ref={ref}
className={cn(
"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
className,
)}
{...props}
>
<ChartStyle id={chartId} config={config} />
<RechartsPrimitive.ResponsiveContainer>{children}</RechartsPrimitive.ResponsiveContainer>
</div>
</ChartContext.Provider>
);
});
ChartContainer.displayName = "Chart";
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
const colorConfig = Object.entries(config).filter(([_, config]) => config.theme || config.color);
if (!colorConfig.length) {
return null;
}
return (
<style
dangerouslySetInnerHTML={{
__html: Object.entries(THEMES)
.map(
([theme, prefix]) => `
${prefix} [data-chart=${id}] {
${colorConfig
.map(([key, itemConfig]) => {
const color = itemConfig.theme?.[theme as keyof typeof itemConfig.theme] || itemConfig.color;
return color ? ` --color-${key}: ${color};` : null;
})
.join("\n")}
}
`,
)
.join("\n"),
}}
/>
);
};
const ChartTooltip = RechartsPrimitive.Tooltip;
const ChartTooltipContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
React.ComponentProps<"div"> & {
hideLabel?: boolean;
hideIndicator?: boolean;
indicator?: "line" | "dot" | "dashed";
nameKey?: string;
labelKey?: string;
}
>(
(
{
active,
payload,
className,
indicator = "dot",
hideLabel = false,
hideIndicator = false,
label,
labelFormatter,
labelClassName,
formatter,
color,
nameKey,
labelKey,
},
ref,
) => {
const { config } = useChart();
const tooltipLabel = React.useMemo(() => {
if (hideLabel || !payload?.length) {
return null;
}
const [item] = payload;
const key = `${labelKey || item.dataKey || item.name || "value"}`;
const itemConfig = getPayloadConfigFromPayload(config, item, key);
const value =
!labelKey && typeof label === "string"
? config[label as keyof typeof config]?.label || label
: itemConfig?.label;
if (labelFormatter) {
return <div className={cn("font-medium", labelClassName)}>{labelFormatter(value, payload)}</div>;
}
if (!value) {
return null;
}
return <div className={cn("font-medium", labelClassName)}>{value}</div>;
}, [label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey]);
if (!active || !payload?.length) {
return null;
}
const nestLabel = payload.length === 1 && indicator !== "dot";
return (
<div
ref={ref}
className={cn(
"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
className,
)}
>
{!nestLabel ? tooltipLabel : null}
<div className="grid gap-1.5">
{payload.map((item, index) => {
const key = `${nameKey || item.name || item.dataKey || "value"}`;
const itemConfig = getPayloadConfigFromPayload(config, item, key);
const indicatorColor = color || item.payload.fill || item.color;
return (
<div
key={item.dataKey}
className={cn(
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
indicator === "dot" && "items-center",
)}
>
{formatter && item?.value !== undefined && item.name ? (
formatter(item.value, item.name, item, index, item.payload)
) : (
<>
{itemConfig?.icon ? (
<itemConfig.icon />
) : (
!hideIndicator && (
<div
className={cn("shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]", {
"h-2.5 w-2.5": indicator === "dot",
"w-1": indicator === "line",
"w-0 border-[1.5px] border-dashed bg-transparent": indicator === "dashed",
"my-0.5": nestLabel && indicator === "dashed",
})}
style={
{
"--color-bg": indicatorColor,
"--color-border": indicatorColor,
} as React.CSSProperties
}
/>
)
)}
<div
className={cn(
"flex flex-1 justify-between leading-none",
nestLabel ? "items-end" : "items-center",
)}
>
<div className="grid gap-1.5">
{nestLabel ? tooltipLabel : null}
<span className="text-muted-foreground">{itemConfig?.label || item.name}</span>
</div>
{item.value && (
<span className="font-mono font-medium tabular-nums text-foreground">
{item.value.toLocaleString()}
</span>
)}
</div>
</>
)}
</div>
);
})}
</div>
</div>
);
},
);
ChartTooltipContent.displayName = "ChartTooltip";
const ChartLegend = RechartsPrimitive.Legend;
const ChartLegendContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> &
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
hideIcon?: boolean;
nameKey?: string;
}
>(({ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey }, ref) => {
const { config } = useChart();
if (!payload?.length) {
return null;
}
return (
<div
ref={ref}
className={cn("flex items-center justify-center gap-4", verticalAlign === "top" ? "pb-3" : "pt-3", className)}
>
{payload.map((item) => {
const key = `${nameKey || item.dataKey || "value"}`;
const itemConfig = getPayloadConfigFromPayload(config, item, key);
return (
<div
key={item.value}
className={cn("flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground")}
>
{itemConfig?.icon && !hideIcon ? (
<itemConfig.icon />
) : (
<div
className="h-2 w-2 shrink-0 rounded-[2px]"
style={{
backgroundColor: item.color,
}}
/>
)}
{itemConfig?.label}
</div>
);
})}
</div>
);
});
ChartLegendContent.displayName = "ChartLegend";
// Helper to extract item config from a payload.
function getPayloadConfigFromPayload(config: ChartConfig, payload: unknown, key: string) {
if (typeof payload !== "object" || payload === null) {
return undefined;
}
const payloadPayload =
"payload" in payload && typeof payload.payload === "object" && payload.payload !== null
? payload.payload
: undefined;
let configLabelKey: string = key;
if (key in payload && typeof payload[key as keyof typeof payload] === "string") {
configLabelKey = payload[key as keyof typeof payload] as string;
} else if (
payloadPayload &&
key in payloadPayload &&
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
) {
configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string;
}
return configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config];
}
export { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent, ChartStyle };

View file

@ -0,0 +1,26 @@
import * as React from "react";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { Check } from "lucide-react";
import { cn } from "@/lib/utils";
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
{...props}
>
<CheckboxPrimitive.Indicator className={cn("flex items-center justify-center text-current")}>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
));
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
export { Checkbox };

View file

@ -0,0 +1,9 @@
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
const Collapsible = CollapsiblePrimitive.Root;
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
export { Collapsible, CollapsibleTrigger, CollapsibleContent };

View file

@ -0,0 +1,132 @@
import * as React from "react";
import { type DialogProps } from "@radix-ui/react-dialog";
import { Command as CommandPrimitive } from "cmdk";
import { Search } from "lucide-react";
import { cn } from "@/lib/utils";
import { Dialog, DialogContent } from "@/components/ui/dialog";
const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
>(({ className, ...props }, ref) => (
<CommandPrimitive
ref={ref}
className={cn(
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
className,
)}
{...props}
/>
));
Command.displayName = CommandPrimitive.displayName;
interface CommandDialogProps extends DialogProps {}
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return (
<Dialog {...props}>
<DialogContent className="overflow-hidden p-0 shadow-lg">
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
</Dialog>
);
};
const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
ref={ref}
className={cn(
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
{...props}
/>
</div>
));
CommandInput.displayName = CommandPrimitive.Input.displayName;
const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => (
<CommandPrimitive.List
ref={ref}
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
{...props}
/>
));
CommandList.displayName = CommandPrimitive.List.displayName;
const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => <CommandPrimitive.Empty ref={ref} className="py-6 text-center text-sm" {...props} />);
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
const CommandGroup = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Group>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Group
ref={ref}
className={cn(
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
className,
)}
{...props}
/>
));
CommandGroup.displayName = CommandPrimitive.Group.displayName;
const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Separator ref={ref} className={cn("-mx-1 h-px bg-border", className)} {...props} />
));
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
const CommandItem = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50",
className,
)}
{...props}
/>
));
CommandItem.displayName = CommandPrimitive.Item.displayName;
const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
return <span className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)} {...props} />;
};
CommandShortcut.displayName = "CommandShortcut";
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
};

View file

@ -0,0 +1,178 @@
import * as React from "react";
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
import { Check, ChevronRight, Circle } from "lucide-react";
import { cn } from "@/lib/utils";
const ContextMenu = ContextMenuPrimitive.Root;
const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
const ContextMenuGroup = ContextMenuPrimitive.Group;
const ContextMenuPortal = ContextMenuPrimitive.Portal;
const ContextMenuSub = ContextMenuPrimitive.Sub;
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
const ContextMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
inset?: boolean;
}
>(({ className, inset, children, ...props }, ref) => (
<ContextMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[state=open]:bg-accent data-[state=open]:text-accent-foreground focus:bg-accent focus:text-accent-foreground",
inset && "pl-8",
className,
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</ContextMenuPrimitive.SubTrigger>
));
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
const ContextMenuSubContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className,
)}
{...props}
/>
));
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
const ContextMenuContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Portal>
<ContextMenuPrimitive.Content
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className,
)}
{...props}
/>
</ContextMenuPrimitive.Portal>
));
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
const ContextMenuItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 focus:bg-accent focus:text-accent-foreground",
inset && "pl-8",
className,
)}
{...props}
/>
));
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
const ContextMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<ContextMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 focus:bg-accent focus:text-accent-foreground",
className,
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.CheckboxItem>
));
ContextMenuCheckboxItem.displayName = ContextMenuPrimitive.CheckboxItem.displayName;
const ContextMenuRadioItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<ContextMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 focus:bg-accent focus:text-accent-foreground",
className,
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.RadioItem>
));
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
const ContextMenuLabel = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Label
ref={ref}
className={cn("px-2 py-1.5 text-sm font-semibold text-foreground", inset && "pl-8", className)}
{...props}
/>
));
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
const ContextMenuSeparator = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-border", className)} {...props} />
));
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
const ContextMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
return <span className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)} {...props} />;
};
ContextMenuShortcut.displayName = "ContextMenuShortcut";
export {
ContextMenu,
ContextMenuTrigger,
ContextMenuContent,
ContextMenuItem,
ContextMenuCheckboxItem,
ContextMenuRadioItem,
ContextMenuLabel,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuGroup,
ContextMenuPortal,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuRadioGroup,
};

View file

@ -0,0 +1,95 @@
import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { X } from "lucide-react";
import { cn } from "@/lib/utils";
const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = DialogPrimitive.Portal;
const DialogClose = DialogPrimitive.Close;
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className,
)}
{...props}
/>
));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className,
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity data-[state=open]:bg-accent data-[state=open]:text-muted-foreground hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
));
DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...props} />
);
DialogHeader.displayName = "DialogHeader";
const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} />
);
DialogFooter.displayName = "DialogFooter";
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold leading-none tracking-tight", className)}
{...props}
/>
));
DialogTitle.displayName = DialogPrimitive.Title.displayName;
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
));
DialogDescription.displayName = DialogPrimitive.Description.displayName;
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogClose,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
};

View file

@ -0,0 +1,87 @@
import * as React from "react";
import { Drawer as DrawerPrimitive } from "vaul";
import { cn } from "@/lib/utils";
const Drawer = ({ shouldScaleBackground = true, ...props }: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
<DrawerPrimitive.Root shouldScaleBackground={shouldScaleBackground} {...props} />
);
Drawer.displayName = "Drawer";
const DrawerTrigger = DrawerPrimitive.Trigger;
const DrawerPortal = DrawerPrimitive.Portal;
const DrawerClose = DrawerPrimitive.Close;
const DrawerOverlay = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Overlay ref={ref} className={cn("fixed inset-0 z-50 bg-black/80", className)} {...props} />
));
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
const DrawerContent = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DrawerPortal>
<DrawerOverlay />
<DrawerPrimitive.Content
ref={ref}
className={cn(
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
className,
)}
{...props}
>
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
{children}
</DrawerPrimitive.Content>
</DrawerPortal>
));
DrawerContent.displayName = "DrawerContent";
const DrawerHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)} {...props} />
);
DrawerHeader.displayName = "DrawerHeader";
const DrawerFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("mt-auto flex flex-col gap-2 p-4", className)} {...props} />
);
DrawerFooter.displayName = "DrawerFooter";
const DrawerTitle = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold leading-none tracking-tight", className)}
{...props}
/>
));
DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
const DrawerDescription = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Description ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
));
DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
export {
Drawer,
DrawerPortal,
DrawerOverlay,
DrawerTrigger,
DrawerClose,
DrawerContent,
DrawerHeader,
DrawerFooter,
DrawerTitle,
DrawerDescription,
};

View file

@ -0,0 +1,179 @@
import * as React from "react";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import { Check, ChevronRight, Circle } from "lucide-react";
import { cn } from "@/lib/utils";
const DropdownMenu = DropdownMenuPrimitive.Root;
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
const DropdownMenuSub = DropdownMenuPrimitive.Sub;
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean;
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[state=open]:bg-accent focus:bg-accent",
inset && "pl-8",
className,
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>
));
DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className,
)}
{...props}
/>
));
DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className,
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
));
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 focus:bg-accent focus:text-accent-foreground",
inset && "pl-8",
className,
)}
{...props}
/>
));
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 focus:bg-accent focus:text-accent-foreground",
className,
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
));
DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 focus:bg-accent focus:text-accent-foreground",
className,
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
));
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
{...props}
/>
));
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} />
));
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
return <span className={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...props} />;
};
DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
};

View file

@ -0,0 +1,129 @@
import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
import { Slot } from "@radix-ui/react-slot";
import { Controller, ControllerProps, FieldPath, FieldValues, FormProvider, useFormContext } from "react-hook-form";
import { cn } from "@/lib/utils";
import { Label } from "@/components/ui/label";
const Form = FormProvider;
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = {
name: TName;
};
const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue);
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
);
};
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext);
const itemContext = React.useContext(FormItemContext);
const { getFieldState, formState } = useFormContext();
const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>");
}
const { id } = itemContext;
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
};
};
type FormItemContextValue = {
id: string;
};
const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue);
const FormItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => {
const id = React.useId();
return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider>
);
},
);
FormItem.displayName = "FormItem";
const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField();
return <Label ref={ref} className={cn(error && "text-destructive", className)} htmlFor={formItemId} {...props} />;
});
FormLabel.displayName = "FormLabel";
const FormControl = React.forwardRef<React.ElementRef<typeof Slot>, React.ComponentPropsWithoutRef<typeof Slot>>(
({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
aria-invalid={!!error}
{...props}
/>
);
},
);
FormControl.displayName = "FormControl";
const FormDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField();
return <p ref={ref} id={formDescriptionId} className={cn("text-sm text-muted-foreground", className)} {...props} />;
},
);
FormDescription.displayName = "FormDescription";
const FormMessage = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField();
const body = error ? String(error?.message) : children;
if (!body) {
return null;
}
return (
<p ref={ref} id={formMessageId} className={cn("text-sm font-medium text-destructive", className)} {...props}>
{body}
</p>
);
},
);
FormMessage.displayName = "FormMessage";
export { useFormField, Form, FormItem, FormLabel, FormControl, FormDescription, FormMessage, FormField };

View file

@ -0,0 +1,27 @@
import * as React from "react";
import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
import { cn } from "@/lib/utils";
const HoverCard = HoverCardPrimitive.Root;
const HoverCardTrigger = HoverCardPrimitive.Trigger;
const HoverCardContent = React.forwardRef<
React.ElementRef<typeof HoverCardPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<HoverCardPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className,
)}
{...props}
/>
));
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
export { HoverCard, HoverCardTrigger, HoverCardContent };

View file

@ -0,0 +1,61 @@
import * as React from "react";
import { OTPInput, OTPInputContext } from "input-otp";
import { Dot } from "lucide-react";
import { cn } from "@/lib/utils";
const InputOTP = React.forwardRef<React.ElementRef<typeof OTPInput>, React.ComponentPropsWithoutRef<typeof OTPInput>>(
({ className, containerClassName, ...props }, ref) => (
<OTPInput
ref={ref}
containerClassName={cn("flex items-center gap-2 has-[:disabled]:opacity-50", containerClassName)}
className={cn("disabled:cursor-not-allowed", className)}
{...props}
/>
),
);
InputOTP.displayName = "InputOTP";
const InputOTPGroup = React.forwardRef<React.ElementRef<"div">, React.ComponentPropsWithoutRef<"div">>(
({ className, ...props }, ref) => <div ref={ref} className={cn("flex items-center", className)} {...props} />,
);
InputOTPGroup.displayName = "InputOTPGroup";
const InputOTPSlot = React.forwardRef<
React.ElementRef<"div">,
React.ComponentPropsWithoutRef<"div"> & { index: number }
>(({ index, className, ...props }, ref) => {
const inputOTPContext = React.useContext(OTPInputContext);
const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index];
return (
<div
ref={ref}
className={cn(
"relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
isActive && "z-10 ring-2 ring-ring ring-offset-background",
className,
)}
{...props}
>
{char}
{hasFakeCaret && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="animate-caret-blink h-4 w-px bg-foreground duration-1000" />
</div>
)}
</div>
);
});
InputOTPSlot.displayName = "InputOTPSlot";
const InputOTPSeparator = React.forwardRef<React.ElementRef<"div">, React.ComponentPropsWithoutRef<"div">>(
({ ...props }, ref) => (
<div ref={ref} role="separator" {...props}>
<Dot />
</div>
),
);
InputOTPSeparator.displayName = "InputOTPSeparator";
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator };

View file

@ -0,0 +1,22 @@
import * as React from "react";
import { cn } from "@/lib/utils";
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className,
)}
ref={ref}
{...props}
/>
);
},
);
Input.displayName = "Input";
export { Input };

View file

@ -0,0 +1,17 @@
import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const labelVariants = cva("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70");
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
));
Label.displayName = LabelPrimitive.Root.displayName;
export { Label };

View file

@ -0,0 +1,207 @@
import * as React from "react";
import * as MenubarPrimitive from "@radix-ui/react-menubar";
import { Check, ChevronRight, Circle } from "lucide-react";
import { cn } from "@/lib/utils";
const MenubarMenu = MenubarPrimitive.Menu;
const MenubarGroup = MenubarPrimitive.Group;
const MenubarPortal = MenubarPrimitive.Portal;
const MenubarSub = MenubarPrimitive.Sub;
const MenubarRadioGroup = MenubarPrimitive.RadioGroup;
const Menubar = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Root
ref={ref}
className={cn("flex h-10 items-center space-x-1 rounded-md border bg-background p-1", className)}
{...props}
/>
));
Menubar.displayName = MenubarPrimitive.Root.displayName;
const MenubarTrigger = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Trigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none data-[state=open]:bg-accent data-[state=open]:text-accent-foreground focus:bg-accent focus:text-accent-foreground",
className,
)}
{...props}
/>
));
MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName;
const MenubarSubTrigger = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
inset?: boolean;
}
>(({ className, inset, children, ...props }, ref) => (
<MenubarPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[state=open]:bg-accent data-[state=open]:text-accent-foreground focus:bg-accent focus:text-accent-foreground",
inset && "pl-8",
className,
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</MenubarPrimitive.SubTrigger>
));
MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName;
const MenubarSubContent = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className,
)}
{...props}
/>
));
MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName;
const MenubarContent = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
>(({ className, align = "start", alignOffset = -4, sideOffset = 8, ...props }, ref) => (
<MenubarPrimitive.Portal>
<MenubarPrimitive.Content
ref={ref}
align={align}
alignOffset={alignOffset}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className,
)}
{...props}
/>
</MenubarPrimitive.Portal>
));
MenubarContent.displayName = MenubarPrimitive.Content.displayName;
const MenubarItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 focus:bg-accent focus:text-accent-foreground",
inset && "pl-8",
className,
)}
{...props}
/>
));
MenubarItem.displayName = MenubarPrimitive.Item.displayName;
const MenubarCheckboxItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<MenubarPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 focus:bg-accent focus:text-accent-foreground",
className,
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.CheckboxItem>
));
MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName;
const MenubarRadioItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<MenubarPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 focus:bg-accent focus:text-accent-foreground",
className,
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.RadioItem>
));
MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName;
const MenubarLabel = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Label
ref={ref}
className={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
{...props}
/>
));
MenubarLabel.displayName = MenubarPrimitive.Label.displayName;
const MenubarSeparator = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} />
));
MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName;
const MenubarShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
return <span className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)} {...props} />;
};
MenubarShortcut.displayname = "MenubarShortcut";
export {
Menubar,
MenubarMenu,
MenubarTrigger,
MenubarContent,
MenubarItem,
MenubarSeparator,
MenubarLabel,
MenubarCheckboxItem,
MenubarRadioGroup,
MenubarRadioItem,
MenubarPortal,
MenubarSubContent,
MenubarSubTrigger,
MenubarGroup,
MenubarSub,
MenubarShortcut,
};

View file

@ -0,0 +1,120 @@
import * as React from "react";
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu";
import { cva } from "class-variance-authority";
import { ChevronDown } from "lucide-react";
import { cn } from "@/lib/utils";
const NavigationMenu = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Root
ref={ref}
className={cn("relative z-10 flex max-w-max flex-1 items-center justify-center", className)}
{...props}
>
{children}
<NavigationMenuViewport />
</NavigationMenuPrimitive.Root>
));
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;
const NavigationMenuList = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.List>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.List
ref={ref}
className={cn("group flex flex-1 list-none items-center justify-center space-x-1", className)}
{...props}
/>
));
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
const NavigationMenuItem = NavigationMenuPrimitive.Item;
const navigationMenuTriggerStyle = cva(
"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50",
);
const NavigationMenuTrigger = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Trigger
ref={ref}
className={cn(navigationMenuTriggerStyle(), "group", className)}
{...props}
>
{children}{" "}
<ChevronDown
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
));
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName;
const NavigationMenuContent = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Content
ref={ref}
className={cn(
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto",
className,
)}
{...props}
/>
));
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName;
const NavigationMenuLink = NavigationMenuPrimitive.Link;
const NavigationMenuViewport = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => (
<div className={cn("absolute left-0 top-full flex justify-center")}>
<NavigationMenuPrimitive.Viewport
className={cn(
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
className,
)}
ref={ref}
{...props}
/>
</div>
));
NavigationMenuViewport.displayName = NavigationMenuPrimitive.Viewport.displayName;
const NavigationMenuIndicator = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Indicator
ref={ref}
className={cn(
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
className,
)}
{...props}
>
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuPrimitive.Indicator>
));
NavigationMenuIndicator.displayName = NavigationMenuPrimitive.Indicator.displayName;
export {
navigationMenuTriggerStyle,
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
};

View file

@ -0,0 +1,81 @@
import * as React from "react";
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
import { cn } from "@/lib/utils";
import { ButtonProps, buttonVariants } from "@/components/ui/button";
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
<nav
role="navigation"
aria-label="pagination"
className={cn("mx-auto flex w-full justify-center", className)}
{...props}
/>
);
Pagination.displayName = "Pagination";
const PaginationContent = React.forwardRef<HTMLUListElement, React.ComponentProps<"ul">>(
({ className, ...props }, ref) => (
<ul ref={ref} className={cn("flex flex-row items-center gap-1", className)} {...props} />
),
);
PaginationContent.displayName = "PaginationContent";
const PaginationItem = React.forwardRef<HTMLLIElement, React.ComponentProps<"li">>(({ className, ...props }, ref) => (
<li ref={ref} className={cn("", className)} {...props} />
));
PaginationItem.displayName = "PaginationItem";
type PaginationLinkProps = {
isActive?: boolean;
} & Pick<ButtonProps, "size"> &
React.ComponentProps<"a">;
const PaginationLink = ({ className, isActive, size = "icon", ...props }: PaginationLinkProps) => (
<a
aria-current={isActive ? "page" : undefined}
className={cn(
buttonVariants({
variant: isActive ? "outline" : "ghost",
size,
}),
className,
)}
{...props}
/>
);
PaginationLink.displayName = "PaginationLink";
const PaginationPrevious = ({ className, ...props }: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink aria-label="Go to previous page" size="default" className={cn("gap-1 pl-2.5", className)} {...props}>
<ChevronLeft className="h-4 w-4" />
<span>Previous</span>
</PaginationLink>
);
PaginationPrevious.displayName = "PaginationPrevious";
const PaginationNext = ({ className, ...props }: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink aria-label="Go to next page" size="default" className={cn("gap-1 pr-2.5", className)} {...props}>
<span>Next</span>
<ChevronRight className="h-4 w-4" />
</PaginationLink>
);
PaginationNext.displayName = "PaginationNext";
const PaginationEllipsis = ({ className, ...props }: React.ComponentProps<"span">) => (
<span aria-hidden className={cn("flex h-9 w-9 items-center justify-center", className)} {...props}>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More pages</span>
</span>
);
PaginationEllipsis.displayName = "PaginationEllipsis";
export {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
};

View file

@ -0,0 +1,29 @@
import * as React from "react";
import * as PopoverPrimitive from "@radix-ui/react-popover";
import { cn } from "@/lib/utils";
const Popover = PopoverPrimitive.Root;
const PopoverTrigger = PopoverPrimitive.Trigger;
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className,
)}
{...props}
/>
</PopoverPrimitive.Portal>
));
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverTrigger, PopoverContent };

View file

@ -0,0 +1,23 @@
import * as React from "react";
import * as ProgressPrimitive from "@radix-ui/react-progress";
import { cn } from "@/lib/utils";
const Progress = React.forwardRef<
React.ElementRef<typeof ProgressPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
className={cn("relative h-4 w-full overflow-hidden rounded-full bg-secondary", className)}
{...props}
>
<ProgressPrimitive.Indicator
className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
));
Progress.displayName = ProgressPrimitive.Root.displayName;
export { Progress };

View file

@ -0,0 +1,36 @@
import * as React from "react";
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
import { Circle } from "lucide-react";
import { cn } from "@/lib/utils";
const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
>(({ className, ...props }, ref) => {
return <RadioGroupPrimitive.Root className={cn("grid gap-2", className)} {...props} ref={ref} />;
});
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;
const RadioGroupItem = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
"aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
{...props}
>
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
<Circle className="h-2.5 w-2.5 fill-current text-current" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
);
});
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;
export { RadioGroup, RadioGroupItem };

Some files were not shown because too many files have changed in this diff Show more