First commit
107
dev/DOMAINS-CONFIG.md
Normal 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.
|
||||
185
dev/FORTIFY-SANCTUM-SETUP.md
Normal 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
191
dev/LOCAL-DEVELOPMENT.md
Normal 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
|
|
@ -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?
|
||||
73
dev/b2in-layout-v10/README.md
Normal 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)
|
||||
BIN
dev/b2in-layout-v10/bun.lockb
Normal file
20
dev/b2in-layout-v10/components.json
Normal 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"
|
||||
}
|
||||
}
|
||||
26
dev/b2in-layout-v10/eslint.config.js
Normal 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",
|
||||
},
|
||||
},
|
||||
);
|
||||
28
dev/b2in-layout-v10/index.html
Normal 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
83
dev/b2in-layout-v10/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
6
dev/b2in-layout-v10/postcss.config.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
BIN
dev/b2in-layout-v10/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
1
dev/b2in-layout-v10/public/placeholder.svg
Normal 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 |
14
dev/b2in-layout-v10/public/robots.txt
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
User-agent: Googlebot
|
||||
Allow: /
|
||||
|
||||
User-agent: Bingbot
|
||||
Allow: /
|
||||
|
||||
User-agent: Twitterbot
|
||||
Allow: /
|
||||
|
||||
User-agent: facebookexternalhit
|
||||
Allow: /
|
||||
|
||||
User-agent: *
|
||||
Allow: /
|
||||
42
dev/b2in-layout-v10/src/App.css
Normal 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;
|
||||
}
|
||||
40
dev/b2in-layout-v10/src/App.tsx
Normal 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;
|
||||
BIN
dev/b2in-layout-v10/src/assets/accommodation-1.jpg
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
dev/b2in-layout-v10/src/assets/accommodation-2.jpg
Normal file
|
After Width: | Height: | Size: 56 KiB |
21
dev/b2in-layout-v10/src/assets/b2in-logo-negative.svg
Normal 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 |
21
dev/b2in-layout-v10/src/assets/b2in-logo-positive.svg
Normal 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 |
BIN
dev/b2in-layout-v10/src/assets/hero-room.jpg
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
dev/b2in-layout-v10/src/assets/marcel-scheibe.jpg
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
dev/b2in-layout-v10/src/assets/room-1.jpg
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
dev/b2in-layout-v10/src/assets/room-2.jpg
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
dev/b2in-layout-v10/src/assets/room-3.jpg
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
dev/b2in-layout-v10/src/assets/sarah-mueller.jpg
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
dev/b2in-layout-v10/src/assets/thomas-weber.jpg
Normal file
|
After Width: | Height: | Size: 26 KiB |
39
dev/b2in-layout-v10/src/components/AboutHero.tsx
Normal 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;
|
||||
91
dev/b2in-layout-v10/src/components/AccommodationSection.tsx
Normal 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;
|
||||
77
dev/b2in-layout-v10/src/components/BrandWorlds.tsx
Normal 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;
|
||||
119
dev/b2in-layout-v10/src/components/BrokerSection.tsx
Normal 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;
|
||||
25
dev/b2in-layout-v10/src/components/CommitmentSection.tsx
Normal 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;
|
||||
83
dev/b2in-layout-v10/src/components/DarkStatsSection.tsx
Normal 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;
|
||||
99
dev/b2in-layout-v10/src/components/DealSection.tsx
Normal 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;
|
||||
52
dev/b2in-layout-v10/src/components/DedicationSection.tsx
Normal 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;
|
||||
179
dev/b2in-layout-v10/src/components/DigitalCore.tsx
Normal 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">< 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;
|
||||
55
dev/b2in-layout-v10/src/components/EcosystemCore.tsx
Normal 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;
|
||||
113
dev/b2in-layout-v10/src/components/EcosystemHero.tsx
Normal 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;
|
||||
58
dev/b2in-layout-v10/src/components/EcosystemStats.tsx
Normal 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;
|
||||
94
dev/b2in-layout-v10/src/components/EndCustomerSection.tsx
Normal 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;
|
||||
74
dev/b2in-layout-v10/src/components/EssentialsSection.tsx
Normal 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;
|
||||
29
dev/b2in-layout-v10/src/components/FinalCTA.tsx
Normal 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;
|
||||
26
dev/b2in-layout-v10/src/components/FinalCommitment.tsx
Normal 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;
|
||||
98
dev/b2in-layout-v10/src/components/Footer.tsx
Normal 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;
|
||||
107
dev/b2in-layout-v10/src/components/Header.tsx
Normal 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;
|
||||
61
dev/b2in-layout-v10/src/components/Hero.tsx
Normal 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;
|
||||
69
dev/b2in-layout-v10/src/components/HesitationsSection.tsx
Normal 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;
|
||||
29
dev/b2in-layout-v10/src/components/JourneyStats.tsx
Normal 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;
|
||||
81
dev/b2in-layout-v10/src/components/LeadershipTeam.tsx
Normal 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;
|
||||
47
dev/b2in-layout-v10/src/components/NewAboutHero.tsx
Normal 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;
|
||||
55
dev/b2in-layout-v10/src/components/OurStory.tsx
Normal 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;
|
||||
71
dev/b2in-layout-v10/src/components/OurValues.tsx
Normal 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;
|
||||
151
dev/b2in-layout-v10/src/components/PartnerBenefits.tsx
Normal 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;
|
||||
53
dev/b2in-layout-v10/src/components/PartnerCTA.tsx
Normal 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;
|
||||
118
dev/b2in-layout-v10/src/components/PartnerHero.tsx
Normal 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;
|
||||
110
dev/b2in-layout-v10/src/components/PartnerProcess.tsx
Normal 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;
|
||||
75
dev/b2in-layout-v10/src/components/PremierBargain.tsx
Normal 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;
|
||||
80
dev/b2in-layout-v10/src/components/QueriesSection.tsx
Normal 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;
|
||||
67
dev/b2in-layout-v10/src/components/RoomGallery.tsx
Normal 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;
|
||||
138
dev/b2in-layout-v10/src/components/RoomRecommendations.tsx
Normal 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;
|
||||
106
dev/b2in-layout-v10/src/components/RoomsGrid.tsx
Normal 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;
|
||||
17
dev/b2in-layout-v10/src/components/RoomsHero.tsx
Normal 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;
|
||||
98
dev/b2in-layout-v10/src/components/SecureSection.tsx
Normal 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;
|
||||
55
dev/b2in-layout-v10/src/components/SpotlightsSection.tsx
Normal 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;
|
||||
35
dev/b2in-layout-v10/src/components/StatsSection.tsx
Normal 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;
|
||||
123
dev/b2in-layout-v10/src/components/SupplierSection.tsx
Normal 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;
|
||||
76
dev/b2in-layout-v10/src/components/TestimonialsSection.tsx
Normal 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;
|
||||
48
dev/b2in-layout-v10/src/components/VisionSection.tsx
Normal 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;
|
||||
52
dev/b2in-layout-v10/src/components/ui/accordion.tsx
Normal 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 };
|
||||
104
dev/b2in-layout-v10/src/components/ui/alert-dialog.tsx
Normal 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,
|
||||
};
|
||||
43
dev/b2in-layout-v10/src/components/ui/alert.tsx
Normal 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 };
|
||||
5
dev/b2in-layout-v10/src/components/ui/aspect-ratio.tsx
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
|
||||
|
||||
const AspectRatio = AspectRatioPrimitive.Root;
|
||||
|
||||
export { AspectRatio };
|
||||
38
dev/b2in-layout-v10/src/components/ui/avatar.tsx
Normal 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 };
|
||||
29
dev/b2in-layout-v10/src/components/ui/badge.tsx
Normal 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 };
|
||||
90
dev/b2in-layout-v10/src/components/ui/breadcrumb.tsx
Normal 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,
|
||||
};
|
||||
47
dev/b2in-layout-v10/src/components/ui/button.tsx
Normal 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 };
|
||||
54
dev/b2in-layout-v10/src/components/ui/calendar.tsx
Normal 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 };
|
||||
43
dev/b2in-layout-v10/src/components/ui/card.tsx
Normal 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 };
|
||||
224
dev/b2in-layout-v10/src/components/ui/carousel.tsx
Normal 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 };
|
||||
303
dev/b2in-layout-v10/src/components/ui/chart.tsx
Normal 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 };
|
||||
26
dev/b2in-layout-v10/src/components/ui/checkbox.tsx
Normal 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 };
|
||||
9
dev/b2in-layout-v10/src/components/ui/collapsible.tsx
Normal 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 };
|
||||
132
dev/b2in-layout-v10/src/components/ui/command.tsx
Normal 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,
|
||||
};
|
||||
178
dev/b2in-layout-v10/src/components/ui/context-menu.tsx
Normal 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,
|
||||
};
|
||||
95
dev/b2in-layout-v10/src/components/ui/dialog.tsx
Normal 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,
|
||||
};
|
||||
87
dev/b2in-layout-v10/src/components/ui/drawer.tsx
Normal 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,
|
||||
};
|
||||
179
dev/b2in-layout-v10/src/components/ui/dropdown-menu.tsx
Normal 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,
|
||||
};
|
||||
129
dev/b2in-layout-v10/src/components/ui/form.tsx
Normal 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 };
|
||||
27
dev/b2in-layout-v10/src/components/ui/hover-card.tsx
Normal 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 };
|
||||
61
dev/b2in-layout-v10/src/components/ui/input-otp.tsx
Normal 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 };
|
||||
22
dev/b2in-layout-v10/src/components/ui/input.tsx
Normal 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 };
|
||||
17
dev/b2in-layout-v10/src/components/ui/label.tsx
Normal 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 };
|
||||
207
dev/b2in-layout-v10/src/components/ui/menubar.tsx
Normal 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,
|
||||
};
|
||||
120
dev/b2in-layout-v10/src/components/ui/navigation-menu.tsx
Normal 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,
|
||||
};
|
||||
81
dev/b2in-layout-v10/src/components/ui/pagination.tsx
Normal 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,
|
||||
};
|
||||
29
dev/b2in-layout-v10/src/components/ui/popover.tsx
Normal 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 };
|
||||
23
dev/b2in-layout-v10/src/components/ui/progress.tsx
Normal 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 };
|
||||
36
dev/b2in-layout-v10/src/components/ui/radio-group.tsx
Normal 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 };
|
||||