mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
@@ -23,7 +23,7 @@ function loadImage(src) {
|
||||
})
|
||||
}
|
||||
|
||||
async function cropToBlob(imageSrc, pixels, outputSize = 512) {
|
||||
async function cropToBlob(imageSrc, pixels, outputSize = 200) {
|
||||
const img = await loadImage(imageSrc)
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = outputSize
|
||||
|
||||
@@ -11,8 +11,8 @@ export default function DashboardGrid({ sections = [] }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="bg-muted/60">
|
||||
<div className="lg:container lg:mx-auto flex flex-col items-start gap-8 p-4">
|
||||
<section className="">
|
||||
<div className="lg:container lg:mx-auto flex flex-col items-start gap-8 p-6">
|
||||
{sections.map(({ title, description, tiles }) => (
|
||||
<div key={title} className="flex flex-col items-start gap-4 w-full">
|
||||
|
||||
@@ -25,7 +25,7 @@ export default function DashboardGrid({ sections = [] }) {
|
||||
</div>
|
||||
|
||||
{/* Tiles */}
|
||||
<div className="grid xs:grid-cols-2 lg:grid-cols-5 h-fit w-full gap-4">
|
||||
<div className="grid xs:grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 h-fit w-full gap-4">
|
||||
{tiles.map(({ key, label, icon: Icon, link }) => (
|
||||
<motion.div
|
||||
key={key}
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
* Type : Reusable Component
|
||||
* Description : Combined date + time picker for deadline fields.
|
||||
* Controlled via a single ISO datetime string (value / onChange).
|
||||
* Date is picked via a Calendar popover; time via a plain time input.
|
||||
* Calendar + time input live inside a Card, matching shadcn's
|
||||
* "Date and Time Picker" pattern.
|
||||
*
|
||||
* Props:
|
||||
* value : string | null — ISO datetime string e.g. "2026-06-01T10:30"
|
||||
@@ -12,12 +13,13 @@
|
||||
***********************************************************************************************************************************************************************/
|
||||
import { useState } from 'react';
|
||||
import { format, parseISO, isValid } from 'date-fns';
|
||||
import { ChevronDownIcon } from 'lucide-react';
|
||||
import { ChevronDownIcon, Clock2Icon } from 'lucide-react';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Calendar } from '@/components/ui/calendar';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Card, CardContent, CardFooter } from '@/components/ui/card';
|
||||
import { Field, FieldGroup, FieldLabel } from '@/components/ui/field';
|
||||
import { InputGroup, InputGroupAddon, InputGroupInput } from '@/components/ui/input-group';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
@@ -51,7 +53,6 @@ export default function DeadlinePicker({ value, onChange, disabled = false }) {
|
||||
|
||||
const handleDateSelect = (date) => {
|
||||
onChange(buildISO(date, timeStr));
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleTimeChange = (e) => {
|
||||
@@ -61,62 +62,67 @@ export default function DeadlinePicker({ value, onChange, disabled = false }) {
|
||||
const handleClear = () => onChange(null);
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-end gap-3">
|
||||
|
||||
{/* ── Date picker ──────────────────────────────────────────────── */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs text-muted-foreground">Date</Label>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
disabled={disabled}
|
||||
className="w-36 justify-between font-normal text-sm"
|
||||
>
|
||||
{selectedDate ? format(selectedDate, 'MMM d, yyyy') : 'Select date'}
|
||||
<ChevronDownIcon className="h-4 w-4 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto overflow-hidden p-0" align="start">
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
disabled={disabled}
|
||||
className="w-56 justify-between font-normal text-sm"
|
||||
>
|
||||
{selectedDate
|
||||
? `${format(selectedDate, 'MMM d, yyyy')} ${format(parseISO(buildISO(selectedDate, timeStr)), 'h:mm a')}`
|
||||
: 'Select date'}
|
||||
<ChevronDownIcon className="h-4 w-4 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto border-0 bg-transparent p-0 shadow-none ring-0" align="start">
|
||||
<Card size="sm" className="w-fit">
|
||||
<CardContent>
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={selectedDate}
|
||||
captionLayout="dropdown"
|
||||
defaultMonth={selectedDate}
|
||||
onSelect={handleDateSelect}
|
||||
disabled={disabled}
|
||||
className="p-0"
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
{/* ── Time input ───────────────────────────────────────────────── */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs text-muted-foreground">Time</Label>
|
||||
<Input
|
||||
type="time"
|
||||
value={timeStr}
|
||||
onChange={handleTimeChange}
|
||||
disabled={disabled || !selectedDate}
|
||||
step="60"
|
||||
className="w-28 appearance-none bg-background
|
||||
[&::-webkit-calendar-picker-indicator]:hidden
|
||||
[&::-webkit-calendar-picker-indicator]:appearance-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* ── Clear ────────────────────────────────────────────────────── */}
|
||||
{value && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
disabled={disabled}
|
||||
onClick={handleClear}
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col items-stretch gap-3 border-t bg-card">
|
||||
<FieldGroup>
|
||||
<Field orientation="horizontal">
|
||||
<FieldLabel htmlFor="deadline-time">Time</FieldLabel>
|
||||
<InputGroup>
|
||||
<InputGroupInput
|
||||
id="deadline-time"
|
||||
type="time"
|
||||
step="60"
|
||||
value={timeStr}
|
||||
onChange={handleTimeChange}
|
||||
disabled={disabled || !selectedDate}
|
||||
className="appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
|
||||
/>
|
||||
<InputGroupAddon>
|
||||
<Clock2Icon className="text-muted-foreground" />
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
{value && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={disabled}
|
||||
onClick={handleClear}
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
)}
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { useProfile } from '@/contexts/ProfileProvider'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Camera, Loader2, Phone, MapPin, Shield, Trophy, Activity, Plus, Trash2, Pencil, Home, Building2, Edit2, Check, X } from 'lucide-react'
|
||||
import AvatarUploadDialog from '@/components/generic/AvatarUploadDialog'
|
||||
import { resolveAssetSrc } from '@/utils/media.util'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
@@ -233,7 +234,7 @@ export default function ProfilePage() {
|
||||
|
||||
const fullName = info?.name?.full_name ?? user?.email ?? 'User'
|
||||
const initials = ((info?.name?.given_name?.[0] ?? '') + (info?.name?.last_name?.[0] ?? '')).toUpperCase() || user?.email?.[0]?.toUpperCase() || 'U'
|
||||
const avatarUrl = info?.avatar?.url ?? null
|
||||
const avatarUrl = resolveAssetSrc(info?.avatar)
|
||||
const avatarColor = AVATAR_COLORS[user?.acc_type] ?? 'bg-muted text-muted-foreground'
|
||||
|
||||
// Load fresh profile data on mount
|
||||
|
||||
Reference in New Issue
Block a user