Apply patch [skip ci]
This commit is contained in:
parent
27bac30445
commit
726358ce8f
23
AGENTS.md
23
AGENTS.md
@ -1,13 +1,17 @@
|
||||
# CyberHybridHub - Deno Fresh Web App Setup Guide
|
||||
|
||||
## Overview
|
||||
CyberHybridHub is a modern web application built with Deno Fresh framework, utilizing Drizzle ORM for database management, TailwindCSS for styling, and designed for Datona sandbox environments.
|
||||
|
||||
CyberHybridHub is a modern web application built with Deno Fresh framework,
|
||||
utilizing Drizzle ORM for database management, TailwindCSS for styling, and
|
||||
designed for Datona sandbox environments.
|
||||
|
||||
<dependencies_and_installation>
|
||||
|
||||
**Package Manager**: Use Deno CLI exclusively (no npm/yarn required)
|
||||
|
||||
**Core Dependencies**:
|
||||
|
||||
- **Deno**: Latest runtime (https://deno.land/)
|
||||
- **Fresh Framework**: `jsr:@fresh/core@^2.2.0`
|
||||
- **Database**: Deno KV (built-in key-value storage)
|
||||
@ -16,18 +20,20 @@ CyberHybridHub is a modern web application built with Deno Fresh framework, util
|
||||
- **Authentication**: Local authentication system
|
||||
|
||||
**Database Setup**:
|
||||
|
||||
- Deno KV is used for Datona sandbox compatibility
|
||||
- Database initialized automatically on startup
|
||||
- No external database server required
|
||||
- Built-in Deno KV storage handles all data persistence
|
||||
|
||||
**External Services**:
|
||||
|
||||
- **Local File Storage**: Used for blob storage in sandbox
|
||||
- **In-Memory Cache**: Built-in Deno KV for caching
|
||||
- No external service dependencies required for sandbox
|
||||
|
||||
**Environment Variables**:
|
||||
Set the following environment variables before running the application:
|
||||
**Environment Variables**: Set the following environment variables before
|
||||
running the application:
|
||||
|
||||
```bash
|
||||
# Server Configuration
|
||||
@ -46,6 +52,7 @@ LOCAL_DEV_MODE="true"
|
||||
```
|
||||
|
||||
**Startup Sequence**:
|
||||
|
||||
1. Install Deno:
|
||||
```bash
|
||||
curl -LO https://github.com/denoland/deno/releases/latest/download/deno-x86_64-unknown-linux-gnu.zip
|
||||
@ -58,6 +65,7 @@ LOCAL_DEV_MODE="true"
|
||||
4. Start development server: `deno task dev` (Deno KV initializes automatically)
|
||||
|
||||
**Available Deno Tasks**:
|
||||
|
||||
- `deno task dev` - Start development server with Vite
|
||||
- `deno task build` - Build for production
|
||||
- `deno task start` - Start production server
|
||||
@ -65,12 +73,14 @@ LOCAL_DEV_MODE="true"
|
||||
- `deno task e2e` - Run Playwright end-to-end tests
|
||||
|
||||
**Development Workflow**:
|
||||
|
||||
1. Make code changes
|
||||
2. Run `deno task check` to validate code quality
|
||||
3. Test with `deno task test` and `deno task e2e`
|
||||
4. Deno KV handles data persistence automatically
|
||||
|
||||
**Datona Sandbox Deployment**:
|
||||
|
||||
1. Set `SANDBOX_MODE="true"` in environment
|
||||
2. Ensure all external API calls are disabled
|
||||
3. Use local file storage instead of cloud services
|
||||
@ -82,6 +92,7 @@ LOCAL_DEV_MODE="true"
|
||||
## Architecture Overview
|
||||
|
||||
### Directory Structure
|
||||
|
||||
- `routes/` - Fresh framework route handlers and pages
|
||||
- `islands/` - Interactive Preact components
|
||||
- `components/` - Reusable UI components
|
||||
@ -93,6 +104,7 @@ LOCAL_DEV_MODE="true"
|
||||
- `drizzle/` - Database migration files
|
||||
|
||||
### Key Technologies
|
||||
|
||||
- **Fresh**: Full-stack web framework with edge-first architecture
|
||||
- **Deno KV**: Built-in key-value storage for data persistence
|
||||
- **TailwindCSS**: Utility-first CSS framework
|
||||
@ -100,6 +112,7 @@ LOCAL_DEV_MODE="true"
|
||||
- **Local Authentication**: JWT-based authentication system
|
||||
|
||||
### Authentication Flow
|
||||
|
||||
1. User registers or logs in via local authentication
|
||||
2. Credentials validated against Deno KV storage
|
||||
3. JWT token generated for session management
|
||||
@ -109,6 +122,7 @@ LOCAL_DEV_MODE="true"
|
||||
## Development Guidelines
|
||||
|
||||
### Code Style
|
||||
|
||||
- Use TypeScript for all new code
|
||||
- Follow Fresh framework conventions
|
||||
- Utilize TailwindCSS for styling
|
||||
@ -116,6 +130,7 @@ LOCAL_DEV_MODE="true"
|
||||
- Write tests for new features and functions
|
||||
|
||||
### Database Operations
|
||||
|
||||
- All data operations use Deno KV key-value storage
|
||||
- No schema migrations required
|
||||
- Automatic data persistence and backup
|
||||
@ -123,10 +138,10 @@ LOCAL_DEV_MODE="true"
|
||||
- Simple API for CRUD operations
|
||||
|
||||
### Security Considerations
|
||||
|
||||
- Never commit secrets or API keys
|
||||
- Use environment variables for sensitive configuration
|
||||
- Implement proper input validation and sanitization
|
||||
- Follow OWASP security guidelines
|
||||
- Use local authentication to avoid external dependencies
|
||||
- Regular security audits and dependency updates
|
||||
|
||||
|
||||
30
TODO.md
30
TODO.md
@ -3,39 +3,50 @@
|
||||
## Code Formatting Issues (deno task check)
|
||||
|
||||
### Critical Issues
|
||||
|
||||
- **5 files not formatted properly** - Need to run `deno fmt` to fix formatting
|
||||
- Files affected:
|
||||
- `routes/tenant/messages/index.tsx` - Import order and formatting issues
|
||||
- `routes/tenant/settings/index.tsx` - Function definition formatting and indentation
|
||||
- `routes/tenant/settings/index.tsx` - Function definition formatting and
|
||||
indentation
|
||||
- `services/messageService.ts` - Import order issues
|
||||
- Additional files with formatting problems (truncated output)
|
||||
|
||||
### Specific Formatting Problems
|
||||
|
||||
1. **Import Statement Order** - Multiple files have incorrect import ordering
|
||||
2. **Function Definition Formatting** - Functions not properly formatted according to Deno standards
|
||||
2. **Function Definition Formatting** - Functions not properly formatted
|
||||
according to Deno standards
|
||||
3. **Indentation Issues** - Inconsistent indentation in JSX components
|
||||
4. **Line Break Issues** - Missing or extra line breaks in various files
|
||||
|
||||
## E2E Test Failures (deno task e2e)
|
||||
|
||||
### Failed Tests
|
||||
|
||||
1. **reminders.spec.ts:4** - "landlord can send reminder and tenant receives it"
|
||||
- **Error**: Element with role "heading" and name matching /messages/i not found
|
||||
- **Error**: Element with role "heading" and name matching /messages/i not
|
||||
found
|
||||
- **Expected**: Messages heading should be visible
|
||||
- **Status**: Timeout after 5000ms
|
||||
|
||||
2. **tenant-dashboard.spec.ts:50** - "tenant messages list shows landlord communication"
|
||||
- **Error**: Element with role "heading" and name matching /messages/i not found
|
||||
2. **tenant-dashboard.spec.ts:50** - "tenant messages list shows landlord
|
||||
communication"
|
||||
- **Error**: Element with role "heading" and name matching /messages/i not
|
||||
found
|
||||
- **Expected**: Messages heading should be visible
|
||||
- **Status**: Timeout after 5000ms
|
||||
|
||||
### Test Infrastructure Issues
|
||||
- **Static File Serving Error**: Server crashes when trying to serve static files
|
||||
|
||||
- **Static File Serving Error**: Server crashes when trying to serve static
|
||||
files
|
||||
- Error: "Failed to read directory: No such file or directory (os error 2)"
|
||||
- Path: `/Users/nanderson/nathanAndrinoid/cyberhybridhub.com/static`
|
||||
- Impact: Tests failing due to missing static assets
|
||||
|
||||
### Root Cause Analysis
|
||||
|
||||
- Both failed tests are looking for a "Messages" heading that doesn't exist
|
||||
- This suggests either:
|
||||
- The messages page/component is not rendering properly
|
||||
@ -45,20 +56,25 @@
|
||||
## Priority Actions
|
||||
|
||||
### High Priority
|
||||
1. **Fix static file serving** - Create missing `/static` directory or fix configuration
|
||||
|
||||
1. **Fix static file serving** - Create missing `/static` directory or fix
|
||||
configuration
|
||||
2. **Investigate messages page** - Check why messages heading is not rendering
|
||||
3. **Run code formatting** - Execute `deno fmt` to fix all formatting issues
|
||||
|
||||
### Medium Priority
|
||||
|
||||
1. **Review test expectations** - Verify if test assertions match actual UI
|
||||
2. **Check navigation flow** - Ensure users can properly navigate to messages
|
||||
3. **Fix import statements** - Correct import order in affected files
|
||||
|
||||
### Low Priority
|
||||
|
||||
1. **Code style consistency** - Ensure all files follow consistent formatting
|
||||
2. **Add error handling** - Improve error handling for missing static files
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Create `/static` directory if missing
|
||||
2. Run `deno fmt` to fix formatting
|
||||
3. Debug messages page rendering issue
|
||||
|
||||
@ -3,10 +3,10 @@ import { define } from "../../../utils.ts";
|
||||
import { page } from "fresh";
|
||||
import { SessionCookie } from "../../auth/callback.ts";
|
||||
import {
|
||||
DashboardMessage,
|
||||
deleteMessagesForUser,
|
||||
getMessagesForUser,
|
||||
markMessagesAsRead,
|
||||
DashboardMessage,
|
||||
MessagePage,
|
||||
} from "../../../services/messageService.ts";
|
||||
import LandlordMessageSelection from "../../../islands/LandlordMessageSelection.tsx";
|
||||
|
||||
@ -49,7 +49,9 @@ export const handler = define.handlers({
|
||||
return page<PropertyPageData>({
|
||||
lease: currentLease,
|
||||
property,
|
||||
propertyPhotos: propertyPhotos.filter((m: PropertyMedia) => m.media_type === "photo"),
|
||||
propertyPhotos: propertyPhotos.filter((m: PropertyMedia) =>
|
||||
m.media_type === "photo"
|
||||
),
|
||||
leaseDocuments,
|
||||
});
|
||||
} catch (error) {
|
||||
@ -148,159 +150,167 @@ function DocumentList({ documents }: { documents: LeaseDocument[] }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default define.page(function TenantPropertyPage({ data }: { data: PropertyPageData }) {
|
||||
const { lease, property, propertyPhotos, leaseDocuments, error } =
|
||||
data as PropertyPageData;
|
||||
export default define.page(
|
||||
function TenantPropertyPage({ data }: { data: PropertyPageData }) {
|
||||
const { lease, property, propertyPhotos, leaseDocuments, error } =
|
||||
data as PropertyPageData;
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div class="min-h-screen bg-gray-50 p-8">
|
||||
<AlertMessage type="error">{error}</AlertMessage>
|
||||
<div class="mt-6">
|
||||
<a
|
||||
href="/tenant/dashboard"
|
||||
class="text-blue-600 hover:text-blue-500 font-medium"
|
||||
>
|
||||
Go to Dashboard
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!property) {
|
||||
return (
|
||||
<div class="min-h-screen bg-gray-50 p-8">
|
||||
<AlertMessage type="error">
|
||||
Property details could not be loaded.
|
||||
</AlertMessage>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const addressLine = formatPropertyAddress(property);
|
||||
const amenities = parseList(property.amenities);
|
||||
const rules = parseList(lease?.late_fee_rules ?? "");
|
||||
const photoItems = (propertyPhotos ?? []).filter(
|
||||
(photo): photo is PropertyMedia & { file_url: string } =>
|
||||
Boolean(photo.file_url),
|
||||
);
|
||||
|
||||
return (
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
{/* Header */}
|
||||
<div class="bg-white shadow">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="py-6">
|
||||
<h1 class="text-3xl font-bold text-gray-900">
|
||||
Your Current Residence
|
||||
</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
{addressLine || "Address not available"}
|
||||
</p>
|
||||
if (error) {
|
||||
return (
|
||||
<div class="min-h-screen bg-gray-50 p-8">
|
||||
<AlertMessage type="error">{error}</AlertMessage>
|
||||
<div class="mt-6">
|
||||
<a
|
||||
href="/tenant/dashboard"
|
||||
class="text-blue-600 hover:text-blue-500 font-medium"
|
||||
>
|
||||
Go to Dashboard
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
{/* Main Content */}
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 py-8 space-y-8">
|
||||
{/* Property Photos */}
|
||||
{photoItems.length > 0 && (
|
||||
<div class="bg-white shadow overflow-hidden sm:rounded-lg p-6">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900 mb-4 border-b pb-2">
|
||||
Property Photos
|
||||
</h3>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
||||
{photoItems.map((photo) => (
|
||||
<img
|
||||
src={photo.file_url}
|
||||
alt={photo.file_name || "Property Photo"}
|
||||
class="w-full h-48 object-cover rounded-lg shadow-md"
|
||||
/>
|
||||
))}
|
||||
if (!property) {
|
||||
return (
|
||||
<div class="min-h-screen bg-gray-50 p-8">
|
||||
<AlertMessage type="error">
|
||||
Property details could not be loaded.
|
||||
</AlertMessage>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const addressLine = formatPropertyAddress(property);
|
||||
const amenities = parseList(property.amenities);
|
||||
const rules = parseList(lease?.late_fee_rules ?? "");
|
||||
const photoItems = (propertyPhotos ?? []).filter(
|
||||
(photo): photo is PropertyMedia & { file_url: string } =>
|
||||
Boolean(photo.file_url),
|
||||
);
|
||||
|
||||
return (
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
{/* Header */}
|
||||
<div class="bg-white shadow">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="py-6">
|
||||
<h1 class="text-3xl font-bold text-gray-900">
|
||||
Your Current Residence
|
||||
</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
{addressLine || "Address not available"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Property Details */}
|
||||
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
Property Details
|
||||
</h3>
|
||||
</div>
|
||||
<div class="border-t border-gray-200">
|
||||
<dl>
|
||||
<DetailItem
|
||||
label="Address"
|
||||
value={addressLine || "Address not available"}
|
||||
/>
|
||||
<DetailItem
|
||||
label="Unit Number"
|
||||
value={property.address_2}
|
||||
/>
|
||||
<DetailItem label="Bedrooms" value={property.bedrooms} />
|
||||
<DetailItem label="Bathrooms" value={property.bathrooms} />
|
||||
<DetailItem
|
||||
label="Square Footage"
|
||||
value={property.square_footage}
|
||||
/>
|
||||
<DetailItem label="Year Built" value={property.year_built} />
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Amenities */}
|
||||
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
Amenities
|
||||
</h3>
|
||||
</div>
|
||||
<div class="border-t border-gray-200 p-6">
|
||||
{amenities.length > 0
|
||||
? (
|
||||
<ul class="list-disc list-inside space-y-1 text-sm text-gray-700">
|
||||
{amenities.map((amenity: string) => <li key={amenity}>{amenity}</li>)}
|
||||
</ul>
|
||||
)
|
||||
: <p class="text-sm text-gray-500">No amenities listed.</p>}
|
||||
</div>
|
||||
</div>
|
||||
{/* Main Content */}
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 py-8 space-y-8">
|
||||
{/* Property Photos */}
|
||||
{photoItems.length > 0 && (
|
||||
<div class="bg-white shadow overflow-hidden sm:rounded-lg p-6">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900 mb-4 border-b pb-2">
|
||||
Property Photos
|
||||
</h3>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
||||
{photoItems.map((photo) => (
|
||||
<img
|
||||
src={photo.file_url}
|
||||
alt={photo.file_name || "Property Photo"}
|
||||
class="w-full h-48 object-cover rounded-lg shadow-md"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Rules */}
|
||||
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
Community Rules
|
||||
</h3>
|
||||
{/* Property Details */}
|
||||
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
Property Details
|
||||
</h3>
|
||||
</div>
|
||||
<div class="border-t border-gray-200">
|
||||
<dl>
|
||||
<DetailItem
|
||||
label="Address"
|
||||
value={addressLine || "Address not available"}
|
||||
/>
|
||||
<DetailItem
|
||||
label="Unit Number"
|
||||
value={property.address_2}
|
||||
/>
|
||||
<DetailItem label="Bedrooms" value={property.bedrooms} />
|
||||
<DetailItem label="Bathrooms" value={property.bathrooms} />
|
||||
<DetailItem
|
||||
label="Square Footage"
|
||||
value={property.square_footage}
|
||||
/>
|
||||
<DetailItem label="Year Built" value={property.year_built} />
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-200 p-6">
|
||||
{rules.length > 0
|
||||
? (
|
||||
<ul class="list-disc list-inside space-y-1 text-sm text-gray-700">
|
||||
{rules.map((rule: string) => <li key={rule}>{rule}</li>)}
|
||||
</ul>
|
||||
)
|
||||
: <p class="text-sm text-gray-500">No specific rules listed.</p>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Lease Documents */}
|
||||
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
Lease Documents
|
||||
</h3>
|
||||
<p class="mt-1 max-w-2xl text-sm text-gray-500">
|
||||
Your current lease agreement and related documents.
|
||||
</p>
|
||||
{/* Amenities */}
|
||||
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
Amenities
|
||||
</h3>
|
||||
</div>
|
||||
<div class="border-t border-gray-200 p-6">
|
||||
{amenities.length > 0
|
||||
? (
|
||||
<ul class="list-disc list-inside space-y-1 text-sm text-gray-700">
|
||||
{amenities.map((amenity: string) => (
|
||||
<li key={amenity}>{amenity}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
: <p class="text-sm text-gray-500">No amenities listed.</p>}
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-200">
|
||||
<DocumentList documents={leaseDocuments || []} />
|
||||
|
||||
{/* Rules */}
|
||||
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
Community Rules
|
||||
</h3>
|
||||
</div>
|
||||
<div class="border-t border-gray-200 p-6">
|
||||
{rules.length > 0
|
||||
? (
|
||||
<ul class="list-disc list-inside space-y-1 text-sm text-gray-700">
|
||||
{rules.map((rule: string) => <li key={rule}>{rule}</li>)}
|
||||
</ul>
|
||||
)
|
||||
: (
|
||||
<p class="text-sm text-gray-500">
|
||||
No specific rules listed.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Lease Documents */}
|
||||
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
Lease Documents
|
||||
</h3>
|
||||
<p class="mt-1 max-w-2xl text-sm text-gray-500">
|
||||
Your current lease agreement and related documents.
|
||||
</p>
|
||||
</div>
|
||||
<div class="border-t border-gray-200">
|
||||
<DocumentList documents={leaseDocuments || []} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@ -56,46 +56,48 @@ export const handler = define.handlers({
|
||||
},
|
||||
});
|
||||
|
||||
export default define.page(function TenantSettingsPage({ data }: { data: SettingsPageData }) {
|
||||
const { user, error } = data as SettingsPageData;
|
||||
export default define.page(
|
||||
function TenantSettingsPage({ data }: { data: SettingsPageData }) {
|
||||
const { user, error } = data as SettingsPageData;
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div class="min-h-screen bg-gray-50 p-8">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<h1 class="text-3xl font-bold text-red-600 mb-4">Error</h1>
|
||||
<p class="text-gray-700">{error}</p>
|
||||
<div class="mt-6">
|
||||
<a
|
||||
href="/tenant/dashboard"
|
||||
class="text-blue-600 hover:text-blue-500 font-medium"
|
||||
>
|
||||
Go to Dashboard
|
||||
</a>
|
||||
if (error) {
|
||||
return (
|
||||
<div class="min-h-screen bg-gray-50 p-8">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<h1 class="text-3xl font-bold text-red-600 mb-4">Error</h1>
|
||||
<p class="text-gray-700">{error}</p>
|
||||
<div class="mt-6">
|
||||
<a
|
||||
href="/tenant/dashboard"
|
||||
class="text-blue-600 hover:text-blue-500 font-medium"
|
||||
>
|
||||
Go to Dashboard
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
{/* Header */}
|
||||
<div class="bg-white shadow">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="py-6">
|
||||
<h1 class="text-3xl font-bold text-gray-900">Tenant Settings</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
Manage your notification and payment preferences.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div class="max-w-4xl mx-auto sm:px-6 lg:px-8 py-8">
|
||||
<TenantSettings user={user} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
{/* Header */}
|
||||
<div class="bg-white shadow">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="py-6">
|
||||
<h1 class="text-3xl font-bold text-gray-900">Tenant Settings</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
Manage your notification and payment preferences.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div class="max-w-4xl mx-auto sm:px-6 lg:px-8 py-8">
|
||||
<TenantSettings user={user} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@ -9,11 +9,11 @@ import {
|
||||
addE2EMessage,
|
||||
deleteE2EMessage,
|
||||
deleteE2EMessagesForUser,
|
||||
getE2EMessageById,
|
||||
getE2EMessageThread,
|
||||
getE2ELeaseById,
|
||||
getE2EMessageById,
|
||||
getE2EMessagesByLease,
|
||||
getE2EMessagesByUser,
|
||||
getE2EMessageThread,
|
||||
getE2EPropertyById,
|
||||
getE2EUserById,
|
||||
isE2EEnabled,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user