Improve portfolio title images, branding, and super-admin domains UI.

Add aspect-ratio crop presets for portfolio title images, fixed card image placeholders with contained centering, Meshkee favicon/logo fallbacks, login branding updates, category FAB alignment, and super-admin website/domain SSL tooling.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-07-23 12:23:02 +03:30
co-authored by Cursor
parent f566387c61
commit db52a83f98
40 changed files with 595 additions and 162 deletions
@@ -55,6 +55,12 @@
object-fit: cover;
}
.previewImgNatural {
width: 100%;
height: auto;
display: block;
}
.removeBtn {
position: absolute;
top: 8px;
@@ -85,9 +91,15 @@
position: relative;
width: 100%;
min-height: 160px;
max-height: 420px;
background: #1e293b;
}
.cropAreaPortrait {
max-height: none;
min-height: 0;
}
.cropControls {
padding: 14px 16px;
display: flex;
@@ -141,6 +153,11 @@
background: var(--primary-dark);
}
.applyBtn:disabled {
opacity: 0.55;
cursor: not-allowed;
}
.changeBtn {
display: inline-flex;
align-self: flex-start;
+38 -7
View File
@@ -1,4 +1,4 @@
import { useCallback, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import Cropper, { type Area } from 'react-easy-crop'
import { ImagePlus, X } from 'lucide-react'
import { getCroppedImage } from '../utils/cropImage'
@@ -7,6 +7,7 @@ import styles from './ImageCropper.module.css'
interface ImageCropperProps {
value: string | null
onChange: (value: string | null) => void
/** Crop aspect ratio. Defaults to square (1). */
aspect?: number
outputFormat?: 'jpeg' | 'png'
accept?: string
@@ -30,6 +31,12 @@ export function ImageCropper({
const [zoom, setZoom] = useState(1)
const [croppedArea, setCroppedArea] = useState<Area | null>(null)
useEffect(() => {
setCrop({ x: 0, y: 0 })
setZoom(1)
setCroppedArea(null)
}, [aspect])
const onCropComplete = useCallback((_: Area, pixels: Area) => {
setCroppedArea(pixels)
}, [])
@@ -38,7 +45,12 @@ export function ImageCropper({
const file = e.target.files?.[0]
if (!file) return
const reader = new FileReader()
reader.onload = () => setImageSrc(reader.result as string)
reader.onload = () => {
setImageSrc(reader.result as string)
setCrop({ x: 0, y: 0 })
setZoom(1)
setCroppedArea(null)
}
reader.readAsDataURL(file)
e.target.value = ''
}
@@ -62,13 +74,26 @@ export function ImageCropper({
onChange(null)
}
const frameStyle = { aspectRatio: `${aspect}` as const }
const isPortrait = aspect < 1
const frameStyle: React.CSSProperties = isPortrait
? {
aspectRatio: `${aspect}`,
height: 'min(480px, 65vh)',
width: 'auto',
maxWidth: '100%',
marginInline: 'auto',
}
: {
aspectRatio: `${aspect}`,
width: '100%',
maxHeight: 'min(420px, 55vh)',
}
return (
<div className={styles.wrapper}>
{value && !imageSrc && (
<div className={styles.preview} style={frameStyle}>
<img src={value} alt="Thumbnail preview" className={styles.previewImg} />
<div className={styles.preview}>
<img src={value} alt="Thumbnail preview" className={styles.previewImgNatural} />
<button type="button" className={styles.removeBtn} onClick={removeThumbnail} aria-label="Remove thumbnail">
<X size={16} />
</button>
@@ -86,8 +111,9 @@ export function ImageCropper({
{imageSrc && (
<div className={styles.cropPanel}>
<div className={styles.cropArea} style={frameStyle}>
<div className={`${styles.cropArea} ${isPortrait ? styles.cropAreaPortrait : ''}`} style={frameStyle}>
<Cropper
key={String(aspect)}
image={imageSrc}
crop={crop}
zoom={zoom}
@@ -113,7 +139,12 @@ export function ImageCropper({
<button type="button" className={styles.cancelBtn} onClick={cancelCrop}>
Cancel
</button>
<button type="button" className={styles.applyBtn} onClick={applyCrop}>
<button
type="button"
className={styles.applyBtn}
onClick={() => void applyCrop()}
disabled={!croppedArea}
>
Apply Crop
</button>
</div>
@@ -34,14 +34,17 @@
width: 100%;
aspect-ratio: 3 / 2;
overflow: hidden;
background: rgba(148, 163, 184, 0.08);
background: transparent;
border-bottom: 1px solid rgba(148, 163, 184, 0.12);
}
.image {
position: relative;
z-index: 1;
width: 100%;
height: 100%;
object-fit: cover;
object-fit: contain;
display: block;
}
.imagePlaceholder {