strade-stx 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +10 -1
- package/contracts/CoreMarketPlace.clar +0 -227
- package/contracts/DisputeResolution_clar.clar +0 -265
- package/contracts/EscrowService.clar +0 -171
- package/contracts/UserProfile.clar +0 -280
- package/contracts/ft-trait.clar +0 -24
- package/contracts/token.clar +0 -178
- package/frontend/README.md +0 -10
- package/frontend/components.json +0 -22
- package/frontend/dist/assets/index-BacuuL66.css +0 -1
- package/frontend/dist/assets/index-jryypd5B.js +0 -194
- package/frontend/dist/favicon.png +0 -0
- package/frontend/dist/index.html +0 -15
- package/frontend/dist/manifest.json +0 -15
- package/frontend/dist/vite.svg +0 -1
- package/frontend/empty-mock.js +0 -1
- package/frontend/eslint.config.js +0 -23
- package/frontend/eslint.config.mjs +0 -25
- package/frontend/index.html +0 -14
- package/frontend/next.config.ts +0 -17
- package/frontend/package-lock.json +0 -14740
- package/frontend/package.json +0 -56
- package/frontend/postcss.config.js +0 -5
- package/frontend/postcss.config.mjs +0 -5
- package/frontend/public/favicon.png +0 -0
- package/frontend/public/file.svg +0 -1
- package/frontend/public/globe.svg +0 -1
- package/frontend/public/manifest.json +0 -15
- package/frontend/public/next.svg +0 -1
- package/frontend/public/vercel.svg +0 -1
- package/frontend/public/vite.svg +0 -1
- package/frontend/public/window.svg +0 -1
- package/frontend/src/App.css +0 -42
- package/frontend/src/App.tsx +0 -177
- package/frontend/src/app/about/page.tsx +0 -208
- package/frontend/src/app/favicon.ico +0 -0
- package/frontend/src/app/globals.css +0 -129
- package/frontend/src/app/help/page.tsx +0 -167
- package/frontend/src/app/how-it-works/page.tsx +0 -274
- package/frontend/src/app/layout.tsx +0 -55
- package/frontend/src/app/marketplace/page.tsx +0 -324
- package/frontend/src/app/my-listings/page.tsx +0 -318
- package/frontend/src/app/page.tsx +0 -15
- package/frontend/src/assets/react.svg +0 -1
- package/frontend/src/components/ConfirmDialog.tsx +0 -54
- package/frontend/src/components/CreateListingForm.tsx +0 -231
- package/frontend/src/components/ErrorBoundary.tsx +0 -73
- package/frontend/src/components/FilterPanel.tsx +0 -10
- package/frontend/src/components/Footer.tsx +0 -100
- package/frontend/src/components/Header.tsx +0 -268
- package/frontend/src/components/ImageUpload.tsx +0 -147
- package/frontend/src/components/LandingPage.tsx +0 -322
- package/frontend/src/components/ListingCard.tsx +0 -154
- package/frontend/src/components/LoadingSkeleton.tsx +0 -44
- package/frontend/src/components/MobileNav.tsx +0 -89
- package/frontend/src/components/NotificationBell.tsx +0 -8
- package/frontend/src/components/NotificationPanel.tsx +0 -14
- package/frontend/src/components/README.md +0 -14
- package/frontend/src/components/SearchBar.tsx +0 -10
- package/frontend/src/components/TestnetBanner.tsx +0 -29
- package/frontend/src/components/ThemeToggle.tsx +0 -32
- package/frontend/src/components/__tests__/Header.test.tsx +0 -70
- package/frontend/src/components/__tests__/ListingCard.test.tsx +0 -86
- package/frontend/src/components/providers/ThemeProvider.tsx +0 -9
- package/frontend/src/components/ui/alert-dialog.tsx +0 -141
- package/frontend/src/components/ui/avatar.tsx +0 -53
- package/frontend/src/components/ui/badge.tsx +0 -46
- package/frontend/src/components/ui/button.tsx +0 -60
- package/frontend/src/components/ui/card.tsx +0 -92
- package/frontend/src/components/ui/dialog.tsx +0 -143
- package/frontend/src/components/ui/dropdown-menu.tsx +0 -257
- package/frontend/src/components/ui/input.tsx +0 -21
- package/frontend/src/components/ui/label.tsx +0 -24
- package/frontend/src/components/ui/select.tsx +0 -187
- package/frontend/src/components/ui/sonner.tsx +0 -40
- package/frontend/src/components/ui/textarea.tsx +0 -18
- package/frontend/src/context/README.md +0 -27
- package/frontend/src/index.css +0 -166
- package/frontend/src/lib/notificationEvents.ts +0 -10
- package/frontend/src/lib/notificationStore.ts +0 -13
- package/frontend/src/lib/notifications.ts +0 -13
- package/frontend/src/lib/search.ts +0 -28
- package/frontend/src/lib/stacks.ts +0 -189
- package/frontend/src/lib/utils.ts +0 -6
- package/frontend/src/main.tsx +0 -10
- package/frontend/src/test/setup.ts +0 -23
- package/frontend/src/types.d.ts +0 -9
- package/frontend/tsconfig.app.json +0 -28
- package/frontend/tsconfig.json +0 -41
- package/frontend/tsconfig.node.json +0 -26
- package/frontend/vercel.json +0 -4
- package/frontend/vite.config.ts +0 -6
- package/frontend/vitest.config.ts +0 -17
- package/scripts/auto-activity.sh +0 -9
- package/scripts/cancel-pending.ts +0 -67
- package/scripts/check-balances.ts +0 -23
- package/scripts/distribute-evenly.ts +0 -56
- package/scripts/drain-accounts.ts +0 -70
- package/scripts/fund-accounts.ts +0 -88
- package/scripts/fund-active.ts +0 -59
- package/scripts/fund-unfunded.ts +0 -88
- package/scripts/generate-activity.ts +0 -181
- package/scripts/git-activity-generator.ts +0 -154
- package/scripts/mobile-server.ts +0 -123
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
AlertDialog,
|
|
5
|
-
AlertDialogAction,
|
|
6
|
-
AlertDialogCancel,
|
|
7
|
-
AlertDialogContent,
|
|
8
|
-
AlertDialogDescription,
|
|
9
|
-
AlertDialogFooter,
|
|
10
|
-
AlertDialogHeader,
|
|
11
|
-
AlertDialogTitle,
|
|
12
|
-
} from '@/components/ui/alert-dialog';
|
|
13
|
-
|
|
14
|
-
interface ConfirmDialogProps {
|
|
15
|
-
open: boolean;
|
|
16
|
-
onOpenChange: (open: boolean) => void;
|
|
17
|
-
title: string;
|
|
18
|
-
description: string;
|
|
19
|
-
onConfirm: () => void;
|
|
20
|
-
confirmText?: string;
|
|
21
|
-
cancelText?: string;
|
|
22
|
-
destructive?: boolean;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export default function ConfirmDialog({
|
|
26
|
-
open,
|
|
27
|
-
onOpenChange,
|
|
28
|
-
title,
|
|
29
|
-
description,
|
|
30
|
-
onConfirm,
|
|
31
|
-
confirmText = 'Confirm',
|
|
32
|
-
cancelText = 'Cancel',
|
|
33
|
-
destructive = false,
|
|
34
|
-
}: ConfirmDialogProps) {
|
|
35
|
-
return (
|
|
36
|
-
<AlertDialog open={open} onOpenChange={onOpenChange}>
|
|
37
|
-
<AlertDialogContent>
|
|
38
|
-
<AlertDialogHeader>
|
|
39
|
-
<AlertDialogTitle>{title}</AlertDialogTitle>
|
|
40
|
-
<AlertDialogDescription>{description}</AlertDialogDescription>
|
|
41
|
-
</AlertDialogHeader>
|
|
42
|
-
<AlertDialogFooter>
|
|
43
|
-
<AlertDialogCancel>{cancelText}</AlertDialogCancel>
|
|
44
|
-
<AlertDialogAction
|
|
45
|
-
onClick={onConfirm}
|
|
46
|
-
className={destructive ? 'bg-red-600 hover:bg-red-700' : ''}
|
|
47
|
-
>
|
|
48
|
-
{confirmText}
|
|
49
|
-
</AlertDialogAction>
|
|
50
|
-
</AlertDialogFooter>
|
|
51
|
-
</AlertDialogContent>
|
|
52
|
-
</AlertDialog>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useState } from 'react';
|
|
4
|
-
import { Button } from '@/components/ui/button';
|
|
5
|
-
import { Input } from '@/components/ui/input';
|
|
6
|
-
import { Label } from '@/components/ui/label';
|
|
7
|
-
import { Textarea } from '@/components/ui/textarea';
|
|
8
|
-
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
|
|
9
|
-
import ImageUpload from '@/components/ImageUpload';
|
|
10
|
-
import { Plus } from 'lucide-react';
|
|
11
|
-
|
|
12
|
-
interface CreateListingFormProps {
|
|
13
|
-
onCreateListing?: (data: {
|
|
14
|
-
name: string;
|
|
15
|
-
description: string;
|
|
16
|
-
price: number;
|
|
17
|
-
duration: number;
|
|
18
|
-
imageUrl?: string;
|
|
19
|
-
}) => void;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export default function CreateListingForm({ onCreateListing }: CreateListingFormProps) {
|
|
23
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
24
|
-
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
25
|
-
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
26
|
-
const [formData, setFormData] = useState({
|
|
27
|
-
name: '',
|
|
28
|
-
description: '',
|
|
29
|
-
price: '',
|
|
30
|
-
duration: '',
|
|
31
|
-
imageUrl: '',
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const validateForm = (): boolean => {
|
|
35
|
-
const newErrors: Record<string, string> = {};
|
|
36
|
-
|
|
37
|
-
if (!formData.name.trim()) {
|
|
38
|
-
newErrors.name = 'Item name is required';
|
|
39
|
-
} else if (formData.name.length < 3) {
|
|
40
|
-
newErrors.name = 'Item name must be at least 3 characters';
|
|
41
|
-
} else if (formData.name.length > 64) {
|
|
42
|
-
newErrors.name = 'Item name must be less than 64 characters';
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (!formData.description.trim()) {
|
|
46
|
-
newErrors.description = 'Description is required';
|
|
47
|
-
} else if (formData.description.length < 10) {
|
|
48
|
-
newErrors.description = 'Description must be at least 10 characters';
|
|
49
|
-
} else if (formData.description.length > 256) {
|
|
50
|
-
newErrors.description = 'Description must be less than 256 characters';
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const price = parseFloat(formData.price);
|
|
54
|
-
if (!formData.price || isNaN(price)) {
|
|
55
|
-
newErrors.price = 'Valid price is required';
|
|
56
|
-
} else if (price <= 0) {
|
|
57
|
-
newErrors.price = 'Price must be greater than 0';
|
|
58
|
-
} else if (price > 1000000) {
|
|
59
|
-
newErrors.price = 'Price is too high';
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const duration = parseInt(formData.duration);
|
|
63
|
-
if (!formData.duration || isNaN(duration)) {
|
|
64
|
-
newErrors.duration = 'Valid duration is required';
|
|
65
|
-
} else if (duration < 1) {
|
|
66
|
-
newErrors.duration = 'Duration must be at least 1 day';
|
|
67
|
-
} else if (duration > 365) {
|
|
68
|
-
newErrors.duration = 'Duration cannot exceed 365 days';
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
setErrors(newErrors);
|
|
72
|
-
return Object.keys(newErrors).length === 0;
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const handleSubmit = async (e: React.FormEvent) => {
|
|
76
|
-
e.preventDefault();
|
|
77
|
-
|
|
78
|
-
if (!validateForm()) return;
|
|
79
|
-
if (!onCreateListing) return;
|
|
80
|
-
|
|
81
|
-
setIsSubmitting(true);
|
|
82
|
-
try {
|
|
83
|
-
const price = parseFloat(formData.price) * 1000000; // Convert to microSTX
|
|
84
|
-
const duration = parseInt(formData.duration) * 144; // Convert days to blocks (approx 10 min per block)
|
|
85
|
-
|
|
86
|
-
await onCreateListing({
|
|
87
|
-
name: formData.name,
|
|
88
|
-
description: formData.description,
|
|
89
|
-
price,
|
|
90
|
-
duration,
|
|
91
|
-
imageUrl: formData.imageUrl,
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// Reset form and close dialog
|
|
95
|
-
setFormData({ name: '', description: '', price: '', duration: '', imageUrl: '' });
|
|
96
|
-
setErrors({});
|
|
97
|
-
setIsOpen(false);
|
|
98
|
-
} catch (error) {
|
|
99
|
-
console.error('Error creating listing:', error);
|
|
100
|
-
} finally {
|
|
101
|
-
setIsSubmitting(false);
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const handleInputChange = (field: string, value: string) => {
|
|
106
|
-
setFormData(prev => ({ ...prev, [field]: value }));
|
|
107
|
-
// Clear error for this field when user starts typing
|
|
108
|
-
if (errors[field]) {
|
|
109
|
-
setErrors(prev => ({ ...prev, [field]: '' }));
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
return (
|
|
114
|
-
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
|
115
|
-
<DialogTrigger asChild>
|
|
116
|
-
<Button className="mb-6">
|
|
117
|
-
<Plus className="h-4 w-4 mr-2" />
|
|
118
|
-
Create Listing
|
|
119
|
-
</Button>
|
|
120
|
-
</DialogTrigger>
|
|
121
|
-
|
|
122
|
-
<DialogContent className="sm:max-w-[500px]">
|
|
123
|
-
<DialogHeader>
|
|
124
|
-
<DialogTitle>Create New Listing</DialogTitle>
|
|
125
|
-
</DialogHeader>
|
|
126
|
-
|
|
127
|
-
<form onSubmit={handleSubmit} className="space-y-4">
|
|
128
|
-
<div>
|
|
129
|
-
<Label>Item Image (Optional)</Label>
|
|
130
|
-
<ImageUpload
|
|
131
|
-
onImageSelect={(url) => handleInputChange('imageUrl', url)}
|
|
132
|
-
currentImage={formData.imageUrl}
|
|
133
|
-
/>
|
|
134
|
-
<p className="text-xs text-slate-500 mt-1">
|
|
135
|
-
Add an image to make your listing more attractive
|
|
136
|
-
</p>
|
|
137
|
-
</div>
|
|
138
|
-
|
|
139
|
-
<div>
|
|
140
|
-
<Label htmlFor="name">Item Name *</Label>
|
|
141
|
-
<Input
|
|
142
|
-
id="name"
|
|
143
|
-
value={formData.name}
|
|
144
|
-
onChange={(e) => handleInputChange('name', e.target.value)}
|
|
145
|
-
placeholder="Enter item name"
|
|
146
|
-
maxLength={64}
|
|
147
|
-
className={errors.name ? 'border-red-500' : ''}
|
|
148
|
-
/>
|
|
149
|
-
{errors.name && (
|
|
150
|
-
<p className="text-xs text-red-500 mt-1">{errors.name}</p>
|
|
151
|
-
)}
|
|
152
|
-
</div>
|
|
153
|
-
|
|
154
|
-
<div>
|
|
155
|
-
<Label htmlFor="description">Description *</Label>
|
|
156
|
-
<Textarea
|
|
157
|
-
id="description"
|
|
158
|
-
value={formData.description}
|
|
159
|
-
onChange={(e) => handleInputChange('description', e.target.value)}
|
|
160
|
-
placeholder="Describe your item"
|
|
161
|
-
maxLength={256}
|
|
162
|
-
rows={3}
|
|
163
|
-
className={errors.description ? 'border-red-500' : ''}
|
|
164
|
-
/>
|
|
165
|
-
{errors.description && (
|
|
166
|
-
<p className="text-xs text-red-500 mt-1">{errors.description}</p>
|
|
167
|
-
)}
|
|
168
|
-
<p className="text-xs text-slate-500 mt-1">
|
|
169
|
-
{formData.description.length}/256 characters
|
|
170
|
-
</p>
|
|
171
|
-
</div>
|
|
172
|
-
|
|
173
|
-
<div className="grid grid-cols-2 gap-4">
|
|
174
|
-
<div>
|
|
175
|
-
<Label htmlFor="price">Price (STX) *</Label>
|
|
176
|
-
<Input
|
|
177
|
-
id="price"
|
|
178
|
-
type="number"
|
|
179
|
-
step="0.000001"
|
|
180
|
-
min="0"
|
|
181
|
-
value={formData.price}
|
|
182
|
-
onChange={(e) => handleInputChange('price', e.target.value)}
|
|
183
|
-
placeholder="0.00"
|
|
184
|
-
className={errors.price ? 'border-red-500' : ''}
|
|
185
|
-
/>
|
|
186
|
-
{errors.price && (
|
|
187
|
-
<p className="text-xs text-red-500 mt-1">{errors.price}</p>
|
|
188
|
-
)}
|
|
189
|
-
</div>
|
|
190
|
-
|
|
191
|
-
<div>
|
|
192
|
-
<Label htmlFor="duration">Duration (days) *</Label>
|
|
193
|
-
<Input
|
|
194
|
-
id="duration"
|
|
195
|
-
type="number"
|
|
196
|
-
min="1"
|
|
197
|
-
max="365"
|
|
198
|
-
value={formData.duration}
|
|
199
|
-
onChange={(e) => handleInputChange('duration', e.target.value)}
|
|
200
|
-
placeholder="30"
|
|
201
|
-
className={errors.duration ? 'border-red-500' : ''}
|
|
202
|
-
/>
|
|
203
|
-
{errors.duration && (
|
|
204
|
-
<p className="text-xs text-red-500 mt-1">{errors.duration}</p>
|
|
205
|
-
)}
|
|
206
|
-
</div>
|
|
207
|
-
</div>
|
|
208
|
-
|
|
209
|
-
<div className="flex justify-end space-x-2 pt-4">
|
|
210
|
-
<Button
|
|
211
|
-
type="button"
|
|
212
|
-
variant="outline"
|
|
213
|
-
onClick={() => setIsOpen(false)}
|
|
214
|
-
disabled={isSubmitting}
|
|
215
|
-
>
|
|
216
|
-
Cancel
|
|
217
|
-
</Button>
|
|
218
|
-
<Button type="submit" disabled={isSubmitting}>
|
|
219
|
-
{isSubmitting ? 'Creating...' : 'Create Listing'}
|
|
220
|
-
</Button>
|
|
221
|
-
</div>
|
|
222
|
-
</form>
|
|
223
|
-
</DialogContent>
|
|
224
|
-
</Dialog>
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
// w-full
|
|
228
|
-
// flex-col
|
|
229
|
-
// h-12
|
|
230
|
-
// font-size: 16px
|
|
231
|
-
// modal overflow
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React from 'react';
|
|
4
|
-
import { Button } from '@/components/ui/button';
|
|
5
|
-
import { AlertCircle } from 'lucide-react';
|
|
6
|
-
|
|
7
|
-
interface ErrorBoundaryProps {
|
|
8
|
-
children: React.ReactNode;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
interface ErrorBoundaryState {
|
|
12
|
-
hasError: boolean;
|
|
13
|
-
error?: Error;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
17
|
-
constructor(props: ErrorBoundaryProps) {
|
|
18
|
-
super(props);
|
|
19
|
-
this.state = { hasError: false };
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
|
23
|
-
return { hasError: true, error };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
|
27
|
-
console.error('Error caught by boundary:', error, errorInfo);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
render() {
|
|
31
|
-
if (this.state.hasError) {
|
|
32
|
-
return (
|
|
33
|
-
<div className="min-h-screen bg-slate-50 flex items-center justify-center p-4">
|
|
34
|
-
<div className="max-w-md w-full bg-white rounded-lg shadow-lg p-8 text-center">
|
|
35
|
-
<div className="mb-6">
|
|
36
|
-
<div className="mx-auto w-16 h-16 bg-red-100 rounded-full flex items-center justify-center">
|
|
37
|
-
<AlertCircle className="h-8 w-8 text-red-600" />
|
|
38
|
-
</div>
|
|
39
|
-
</div>
|
|
40
|
-
|
|
41
|
-
<h1 className="text-2xl font-bold text-slate-900 mb-2">
|
|
42
|
-
Something went wrong
|
|
43
|
-
</h1>
|
|
44
|
-
|
|
45
|
-
<p className="text-slate-600 mb-6">
|
|
46
|
-
We encountered an unexpected error. Please try refreshing the page.
|
|
47
|
-
</p>
|
|
48
|
-
|
|
49
|
-
{this.state.error && (
|
|
50
|
-
<details className="text-left mb-6 p-4 bg-slate-50 rounded text-sm">
|
|
51
|
-
<summary className="cursor-pointer font-medium text-slate-700 mb-2">
|
|
52
|
-
Error details
|
|
53
|
-
</summary>
|
|
54
|
-
<pre className="text-xs text-slate-600 overflow-auto">
|
|
55
|
-
{this.state.error.toString()}
|
|
56
|
-
</pre>
|
|
57
|
-
</details>
|
|
58
|
-
)}
|
|
59
|
-
|
|
60
|
-
<Button
|
|
61
|
-
onClick={() => window.location.reload()}
|
|
62
|
-
className="w-full"
|
|
63
|
-
>
|
|
64
|
-
Refresh Page
|
|
65
|
-
</Button>
|
|
66
|
-
</div>
|
|
67
|
-
</div>
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return this.props.children;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import Link from 'next/link';
|
|
2
|
-
import { Github, ExternalLink } from 'lucide-react';
|
|
3
|
-
|
|
4
|
-
export default function Footer() {
|
|
5
|
-
return (
|
|
6
|
-
<footer className="bg-gray-50 dark:bg-gray-950 border-t border-gray-200 dark:border-gray-800 mt-16">
|
|
7
|
-
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
8
|
-
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
|
9
|
-
<div>
|
|
10
|
-
<h3 className="text-lg font-semibold text-black dark:text-white mb-4">Strade</h3>
|
|
11
|
-
<p className="text-gray-600 dark:text-gray-400 text-sm">
|
|
12
|
-
A decentralized marketplace built on Stacks blockchain.
|
|
13
|
-
Buy and sell goods securely with smart contracts.
|
|
14
|
-
</p>
|
|
15
|
-
</div>
|
|
16
|
-
|
|
17
|
-
<div>
|
|
18
|
-
<h4 className="font-semibold text-black dark:text-white mb-4">Quick Links</h4>
|
|
19
|
-
<ul className="space-y-2 text-sm text-gray-600 dark:text-gray-400">
|
|
20
|
-
<li>
|
|
21
|
-
<Link href="/marketplace" className="hover:text-black dark:hover:text-white transition-colors">
|
|
22
|
-
Marketplace
|
|
23
|
-
</Link>
|
|
24
|
-
</li>
|
|
25
|
-
<li>
|
|
26
|
-
<Link href="/my-listings" className="hover:text-black dark:hover:text-white transition-colors">
|
|
27
|
-
My Listings
|
|
28
|
-
</Link>
|
|
29
|
-
</li>
|
|
30
|
-
<li>
|
|
31
|
-
<Link href="/about" className="hover:text-black dark:hover:text-white transition-colors">
|
|
32
|
-
About Us
|
|
33
|
-
</Link>
|
|
34
|
-
</li>
|
|
35
|
-
<li>
|
|
36
|
-
<Link href="/how-it-works" className="hover:text-black dark:hover:text-white transition-colors">
|
|
37
|
-
How It Works
|
|
38
|
-
</Link>
|
|
39
|
-
</li>
|
|
40
|
-
<li>
|
|
41
|
-
<Link href="/help" className="hover:text-black dark:hover:text-white transition-colors">
|
|
42
|
-
Help & FAQ
|
|
43
|
-
</Link>
|
|
44
|
-
</li>
|
|
45
|
-
</ul>
|
|
46
|
-
</div>
|
|
47
|
-
|
|
48
|
-
<div>
|
|
49
|
-
<h4 className="font-semibold text-black dark:text-white mb-4">Resources</h4>
|
|
50
|
-
<ul className="space-y-2 text-sm text-gray-600 dark:text-gray-400">
|
|
51
|
-
<li>
|
|
52
|
-
<a
|
|
53
|
-
href="https://www.stacks.co/"
|
|
54
|
-
target="_blank"
|
|
55
|
-
rel="noopener noreferrer"
|
|
56
|
-
className="hover:text-black dark:hover:text-white transition-colors inline-flex items-center gap-1"
|
|
57
|
-
>
|
|
58
|
-
Stacks Blockchain
|
|
59
|
-
<ExternalLink className="h-3 w-3" />
|
|
60
|
-
</a>
|
|
61
|
-
</li>
|
|
62
|
-
<li>
|
|
63
|
-
<a
|
|
64
|
-
href="https://explorer.hiro.so/?chain=testnet"
|
|
65
|
-
target="_blank"
|
|
66
|
-
rel="noopener noreferrer"
|
|
67
|
-
className="hover:text-black dark:hover:text-white transition-colors inline-flex items-center gap-1"
|
|
68
|
-
>
|
|
69
|
-
Testnet Explorer
|
|
70
|
-
<ExternalLink className="h-3 w-3" />
|
|
71
|
-
</a>
|
|
72
|
-
</li>
|
|
73
|
-
<li>
|
|
74
|
-
<a
|
|
75
|
-
href="https://github.com"
|
|
76
|
-
target="_blank"
|
|
77
|
-
rel="noopener noreferrer"
|
|
78
|
-
className="hover:text-black dark:hover:text-white transition-colors inline-flex items-center gap-1"
|
|
79
|
-
>
|
|
80
|
-
<Github className="h-3 w-3" />
|
|
81
|
-
GitHub
|
|
82
|
-
</a>
|
|
83
|
-
</li>
|
|
84
|
-
</ul>
|
|
85
|
-
</div>
|
|
86
|
-
</div>
|
|
87
|
-
|
|
88
|
-
<div className="border-t border-gray-200 dark:border-gray-800 mt-8 pt-8 text-center text-sm text-gray-600 dark:text-gray-400">
|
|
89
|
-
<p>© 2025 Strade. Built on Stacks blockchain. All rights reserved.</p>
|
|
90
|
-
<p className="mt-2 text-xs text-gray-500 dark:text-gray-500">
|
|
91
|
-
Testnet version - For demonstration purposes only
|
|
92
|
-
</p>
|
|
93
|
-
</div>
|
|
94
|
-
</div>
|
|
95
|
-
</footer>
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
// grid-cols-1 md:grid-cols-4
|
|
99
|
-
// text-center md:text-left
|
|
100
|
-
// py-8
|