1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129import React from 'react';
import { QueryClient, useQuery } from '@tanstack/react-query';
import { Link, useLoaderData } from 'react-router-dom';
import { SEO } from '~/components/layout';
import { propertiesQuery } from '~/queries';
import { LoadingProperties } from './loading';
export const propertiesLoader = (queryClient: QueryClient) => async () => {
const initialProperties =
await queryClient.ensureQueryData(propertiesQuery());
return { initialProperties };
};
const formatDate = (isoString: string): string => {
const date = new Date(isoString);
return new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: true,
}).format(date);
};
interface Property {
id: string;
name: string;
lastProcessed: string;
}
const PropertyCard: React.FC<Property> = ({ id, name, lastProcessed }) => (
<article
aria-labelledby={`property-title-${id}`}
className="rounded-lg bg-white p-4 shadow"
>
<h2 id={`property-title-${id}`} className="text-lg">
{name}
</h2>
<div className="mt-2 flex flex-col justify-between gap-3 md:flex-row md:items-end">
<div className="min-w-0">
<p className="mt-1 text-sm text-gray-600">
Added {formatDate(lastProcessed)}
</p>
</div>
<Link
to={`/properties/${id}/edit`}
className="inline-flex h-9 justify-center whitespace-nowrap rounded-md bg-[#663808] px-4 py-2 text-sm text-white shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[#1D781D] focus-visible:ring-offset-2"
>
<span className="sr-only">{`Edit ${name} Property`}</span>
<span aria-hidden="true">Edit Property</span>
</Link>
</div>
</article>
);
const Properties = () => {
const { initialProperties } = useLoaderData() as Awaited<
ReturnType<ReturnType<typeof propertiesLoader>>
>;
const {
data: properties,
isLoading,
error,
} = useQuery({
...propertiesQuery(),
initialData: initialProperties,
});
if (error) return <div>Error: {error.message}</div>;
return (
<>
<SEO
title="Properties - Equalify"
description="Manage and monitor your properties on Equalify to improve their accessibility."
url="https://dashboard.equalify.app/properties"
/>
<div className="flex w-full flex-col-reverse justify-between sm:flex-row sm:items-center">
<h1
className="text-2xl font-bold md:text-3xl"
id="properties-list-heading"
>
Properties
</h1>
{properties.length > 0 && (
<Link
to="/properties/add"
className="inline-flex h-9 items-center justify-end place-self-end whitespace-nowrap rounded-md bg-[#005031] px-4 py-3 text-base text-white shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[#1D781D] focus-visible:ring-offset-2 max-sm:w-fit max-sm:px-3 max-sm:py-2.5"
>
Add Property
</Link>
)}
</div>
{isLoading ? (
<LoadingProperties />
) : properties.length === 0 ? (
<div className="mt-7 text-center">
<h2 className="text-xl font-semibold text-gray-700">
No Properties Added
</h2>
<p className="mt-2 text-gray-600">
You haven't added any properties yet. Get started by adding your
first property and monitor its accessibility status.
</p>
<Link
to="/properties/add"
className="mt-4 inline-flex h-9 items-center justify-center whitespace-nowrap rounded-md bg-[#005031] px-4 py-2 text-sm text-white shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[#1D781D] focus-visible:ring-offset-2"
>
Add Your First Property
</Link>
</div>
) : (
<section
aria-labelledby="properties-list-heading"
className="mt-7 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3"
>
{properties.map((property: Property) => (
<PropertyCard key={property.id} {...property} />
))}
</section>
)}
</>
);
};
export default Properties;