Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions packages/console/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,44 @@
@tailwind components;
@tailwind utilities;

/* Custom loader animations */
@keyframes spin-dash {
0% {
stroke-dasharray: 1, 200;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 90, 200;
stroke-dashoffset: -35px;
}
100% {
stroke-dasharray: 90, 200;
stroke-dashoffset: -124px;
}
}

@keyframes pulse-dot {
0%, 80%, 100% {
transform: scale(0);
opacity: 0.5;
}
40% {
transform: scale(1);
opacity: 1;
}
}

@keyframes pulse-scale {
0%, 100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.5;
transform: scale(0.8);
}
}

@import 'react-tooltip/dist/react-tooltip.css';

:root {
Expand Down Expand Up @@ -86,4 +124,30 @@
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}

/* Professional UI utilities */
.card-shadow {
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
}
.card-shadow-lg {
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
}
.card-shadow-hover {
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
}
.transition-all-smooth {
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.bg-professional {
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
}
.bg-professional-branded {
background: linear-gradient(135deg, rgba(248, 250, 252, 0.9) 0%, rgba(241, 245, 249, 0.9) 100%), url("/racha-fire.png") bottom left;
background-size: 100% auto, 100% auto;
background-position: center, bottom;
background-repeat: no-repeat, no-repeat;
}
.sidebar-professional {
background: linear-gradient(180deg, #374151 0%, #1f2937 100%);
}
}
14 changes: 7 additions & 7 deletions packages/console/src/app/migration/create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function ChooseSource ({ config, onNext }: WizardProps) {
</button>
))}
</div>
<button onClick={handleNextClick} className={`inline-block bg-hot-red border border-hot-red font-epilogue text-white uppercase text-sm px-6 py-2 rounded-full whitespace-nowrap ${source ? 'hover:bg-white hover:text-hot-red' : 'opacity-10'}`} disabled={!source}>
<button onClick={handleNextClick} className={`inline-block bg-hot-red border border-hot-red font-epilogue text-white uppercase text-sm px-6 py-2 rounded-lg whitespace-nowrap ${source ? 'hover:bg-white hover:text-hot-red' : 'opacity-10'}`} disabled={!source}>
Next <ChevronRightIcon className='h-5 w-5 inline-block ml-1 align-middle' style={{marginTop: -4}} />
</button>
</div>
Expand Down Expand Up @@ -116,10 +116,10 @@ function AddSourceToken ({ config, onNext, onPrev }: WizardProps) {
/>
<p className='text-xs text-red-700'>{error}</p>
</div>
<button onClick={e => { e.preventDefault(); onPrev() }} className={`inline-block bg-hot-red border border-hot-red font-epilogue text-white uppercase text-sm mr-2 px-6 py-2 rounded-full whitespace-nowrap hover:bg-white hover:text-hot-red`}>
<button onClick={e => { e.preventDefault(); onPrev() }} className={`inline-block bg-hot-red border border-hot-red font-epilogue text-white uppercase text-sm mr-2 px-6 py-2 rounded-lg whitespace-nowrap hover:bg-white hover:text-hot-red`}>
<ChevronLeftIcon className='h-5 w-5 inline-block mr-1 align-middle' style={{marginTop: -4}}/> Previous
</button>
<button onClick={handleNextClick} className={`inline-block bg-hot-red border border-hot-red font-epilogue text-white uppercase text-sm px-6 py-2 rounded-full whitespace-nowrap ${token ? 'hover:bg-white hover:text-hot-red' : 'opacity-10'}`} disabled={!token || checking}>
<button onClick={handleNextClick} className={`inline-block bg-hot-red border border-hot-red font-epilogue text-white uppercase text-sm px-6 py-2 rounded-lg whitespace-nowrap ${token ? 'hover:bg-white hover:text-hot-red' : 'opacity-10'}`} disabled={!token || checking}>
{checking
? <><ArrowPathIcon className={`h-5 w-5 animate-spin inline-block mr-1 align-middle`}/>Checking...</>
: <>Next <ChevronRightIcon className='h-5 w-5 inline-block ml-1 align-middle' style={{marginTop: -4}}/></>}
Expand Down Expand Up @@ -162,10 +162,10 @@ function ChooseTargetSpace ({ config, onNext, onPrev }: WizardProps) {
</button>
))}
</div>
<button onClick={e => { e.preventDefault(); onPrev() }} className={`inline-block bg-hot-red border border-hot-red font-epilogue text-white uppercase text-sm mr-2 px-6 py-2 rounded-full whitespace-nowrap hover:bg-white hover:text-hot-red`}>
<button onClick={e => { e.preventDefault(); onPrev() }} className={`inline-block bg-hot-red border border-hot-red font-epilogue text-white uppercase text-sm mr-2 px-6 py-2 rounded-lg whitespace-nowrap hover:bg-white hover:text-hot-red`}>
<ChevronLeftIcon className='h-5 w-5 inline-block mr-1 align-middle' style={{marginTop: -4}}/> Previous
</button>
<button onClick={handleNextClick} className={`inline-block bg-hot-red border border-hot-red font-epilogue text-white uppercase text-sm px-6 py-2 rounded-full whitespace-nowrap ${space ? 'hover:bg-white hover:text-hot-red' : 'opacity-10'}`} disabled={!space}>
<button onClick={handleNextClick} className={`inline-block bg-hot-red border border-hot-red font-epilogue text-white uppercase text-sm px-6 py-2 rounded-lg whitespace-nowrap ${space ? 'hover:bg-white hover:text-hot-red' : 'opacity-10'}`} disabled={!space}>
Next <ChevronRightIcon className='h-5 w-5 inline-block ml-1 align-middle' style={{marginTop: -4}}/>
</button>
</div>
Expand Down Expand Up @@ -208,10 +208,10 @@ function Confirmation ({ config, onNext, onPrev }: WizardProps) {
</div>
</div>

<button onClick={e => { e.preventDefault(); onPrev() }} className={`inline-block bg-hot-red border border-hot-red font-epilogue text-white uppercase text-sm mr-2 px-6 py-2 rounded-full whitespace-nowrap hover:bg-white hover:text-hot-red`}>
<button onClick={e => { e.preventDefault(); onPrev() }} className={`inline-block bg-hot-red border border-hot-red font-epilogue text-white uppercase text-sm mr-2 px-6 py-2 rounded-lg whitespace-nowrap hover:bg-white hover:text-hot-red`}>
<ChevronLeftIcon className='h-5 w-5 inline-block mr-1 align-middle' style={{marginTop: -4}}/> Previous
</button>
<button onClick={handleNextClick} className={`inline-block bg-hot-red border border-hot-red font-epilogue text-white uppercase text-sm px-6 py-2 rounded-full whitespace-nowrap hover:bg-white hover:text-hot-red`}>
<button onClick={handleNextClick} className={`inline-block bg-hot-red border border-hot-red font-epilogue text-white uppercase text-sm px-6 py-2 rounded-lg whitespace-nowrap hover:bg-white hover:text-hot-red`}>
Start <ChevronRightIcon className='h-5 w-5 inline-block ml-1 align-middle' style={{marginTop: -4}}/>
</button>
</div>
Expand Down
53 changes: 34 additions & 19 deletions packages/console/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,26 +39,41 @@ export function SpacePage() {
}

return (
<>
<div className="max-w-7xl mx-auto">
<SpacesNav />
<H1>Spaces</H1>
<SpacesTabNavigation
activeTab={activeTab}
onTabChange={setActiveTab}
showPrivateTab={shouldShowPrivateSpacesTab}
privateTabLocked={!canAccessPrivateSpaces}
/>
{activeTab === 'public' && (
<SpacesList spaces={publicSpaces} type="public" />
)}
{activeTab === 'private' && (
canAccessPrivateSpaces ? (
<SpacesList spaces={privateSpaces} type="private" />
) : (
<UpgradePrompt hasHiddenSpaces={hasHiddenPrivateSpaces} />
)
)}
</>

{/* Professional Header */}
<div className="mb-6">
<H1 className="text-2xl md:text-3xl font-bold text-slate-900 mb-1">Your Spaces</H1>
<p className="text-sm md:text-lg text-slate-800 font-medium">
Manage your storage spaces and organize your files with ease.
</p>
</div>

{/* Tab Navigation */}
<div className="mb-8">
<SpacesTabNavigation
activeTab={activeTab}
onTabChange={setActiveTab}
showPrivateTab={shouldShowPrivateSpacesTab}
privateTabLocked={!canAccessPrivateSpaces}
/>
</div>

{/* Content Area */}
<div className="min-h-[400px]">
{activeTab === 'public' && (
<SpacesList spaces={publicSpaces} type="public" />
)}
{activeTab === 'private' && (
canAccessPrivateSpaces ? (
<SpacesList spaces={privateSpaces} type="private" />
) : (
<UpgradePrompt hasHiddenSpaces={hasHiddenPrivateSpaces} />
)
)}
</div>
</div>
)
}

12 changes: 6 additions & 6 deletions packages/console/src/app/plans/change/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,13 @@ function PlanSection ({ account, planID, planName, planLabel, flatFee, flatFeeAl
<DefaultLoader className='h-6 w-6' />
) : (
isCurrentPlan ? (
<button className={`inline-block border border-hot-red bg-white text-hot-red font-epilogue uppercase text-xs sm:text-sm px-4 sm:px-6 py-2 rounded-full whitespace-nowrap cursor-not-allowed opacity-75`} disabled={true}>
<button className={`inline-block border border-hot-red bg-white text-hot-red font-epilogue uppercase text-xs sm:text-sm px-4 sm:px-6 py-2 rounded-lg whitespace-nowrap cursor-not-allowed opacity-75`} disabled={true}>
<CheckIcon className='h-4 w-4 sm:h-5 sm:w-5 inline-block mr-1 align-middle' style={{marginTop: -4}} /> Current Plan
</button>
) : (
<button
onClick={() => selectPlan(planID)}
className={`inline-block bg-hot-red border border-hot-red hover:bg-white hover:text-hot-red font-epilogue text-white uppercase text-xs sm:text-sm px-4 sm:px-6 py-2 rounded-full whitespace-nowrap ${isCurrentPlan ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'}`}
className={`inline-block bg-hot-red border border-hot-red hover:bg-white hover:text-hot-red font-epilogue text-white uppercase text-xs sm:text-sm px-4 sm:px-6 py-2 rounded-lg whitespace-nowrap ${isCurrentPlan ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'}`}
disabled={isCurrentPlan || isLoading || isUpdatingPlan}
>
<RocketLaunchIcon className='h-4 w-4 sm:h-5 sm:w-5 inline-block mr-1 align-middle' style={{marginTop: -4}} /> {currentPlanID && buttonText(currentPlanID, planID)}
Expand Down Expand Up @@ -185,7 +185,7 @@ function DelegatePlanCreateAdminSessionForm ({ className = '', account }: { clas
placeholder='To Email' type='email'
{...register('email')} />
</label>
<input className='inline-block bg-hot-red border border-hot-red hover:bg-white hover:text-hot-red font-epilogue text-white uppercase text-sm mr-2 px-6 py-2 rounded-full whitespace-nowrap cursor-pointer' type='submit' value='Delegate' />
<input className='inline-block bg-white border border-hot-red hover:bg-hot-red hover:text-white font-epilogue text-hot-red uppercase text-sm mr-2 px-6 py-2 rounded-lg whitespace-nowrap cursor-pointer' type='submit' value='Delegate' />
</form>
)
}
Expand All @@ -196,16 +196,16 @@ function CustomerPortalLink ({ did }: { did: AccountDID }) {
<>
{customerPortalLink ? (
<div className='flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-2'>
<button className='inline-block bg-hot-red border border-hot-red hover:bg-white hover:text-hot-red font-epilogue text-white uppercase text-sm px-6 py-2 rounded-full whitespace-nowrap' onClick={() => generateCustomerPortalLink(did)} disabled={generatingCustomerPortalLink}>
<button className='inline-block bg-white border border-hot-red hover:bg-hot-red hover:text-white font-epilogue text-hot-red uppercase text-sm px-6 py-2 rounded-lg whitespace-nowrap' onClick={() => generateCustomerPortalLink(did)} disabled={generatingCustomerPortalLink}>
<ArrowPathIcon className={`h-5 w-5 inline-block align-middle ${generatingCustomerPortalLink ? 'animate-spin' : ''}`} />
</button>
<a className='inline-block bg-hot-red border border-hot-red hover:bg-white hover:text-hot-red font-epilogue text-white uppercase text-sm px-6 py-2 rounded-full whitespace-nowrap text-center' href={customerPortalLink} target="_blank" rel="noopener noreferrer">
<a className='inline-block bg-white border border-hot-red hover:bg-hot-red hover:text-white font-epilogue text-hot-red uppercase text-sm px-6 py-2 rounded-lg whitespace-nowrap text-center' href={customerPortalLink} target="_blank" rel="noopener noreferrer">
Open Billing Portal
<ArrowTopRightOnSquareIcon className='relative inline h-5 w-4 ml-1 -mt-1' />
</a>
</div>
) : (
<button onClick={() => generateCustomerPortalLink(did)} disabled={generatingCustomerPortalLink} className='inline-block bg-hot-red border border-hot-red hover:bg-white hover:text-hot-red font-epilogue text-white uppercase text-sm px-6 py-2 rounded-full whitespace-nowrap'>
<button onClick={() => generateCustomerPortalLink(did)} disabled={generatingCustomerPortalLink} className='inline-block bg-white border border-hot-red hover:bg-hot-red hover:text-white font-epilogue text-hot-red uppercase text-sm px-6 py-2 rounded-lg whitespace-nowrap'>
{generatingCustomerPortalLink ? <ArrowPathIcon className='h-5 w-5 inline-block mr-1 align-middle animate-spin' style={{marginTop: -4}} /> : <LinkIcon className='h-5 w-5 inline-block mr-1 align-middle' style={{marginTop: -4}} />} Generate Link
</button>
)}
Expand Down
32 changes: 28 additions & 4 deletions packages/console/src/app/space/[did]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,18 @@ import { PropsWithChildren } from 'react'
import { useW3 } from '@storacha/ui-react'
import { DidIcon } from '@/components/DidIcon'
import { Nav, NavLink } from '@/components/Nav'
import { QueueListIcon, ShareIcon, CloudArrowUpIcon } from '@heroicons/react/24/outline'
import { QueueListIcon, ShareIcon, CloudArrowUpIcon, ClipboardIcon } from '@heroicons/react/24/outline'

export function truncateDid(did: string): string {
// For mobile: show did:key:first7...last7
if (did.startsWith('did:key:')) {
const keyPart = did.substring(8) // Remove 'did:key:'
if (keyPart.length > 14) {
return `did:key:${keyPart.substring(0, 7)}...${keyPart.substring(keyPart.length - 7)}`
}
}
return did
}

interface LayoutProps extends PropsWithChildren {
params: {
Expand Down Expand Up @@ -47,9 +58,22 @@ export default function Layout ({children, params}: LayoutProps): JSX.Element {
{space.access?.type === 'private' ? 'Private' : 'Public'}
</span>
</div>
<label className='font-mono text-xs'>
{space.did()}
</label>
<div className='flex items-center gap-2 min-w-0'>
<label className='font-mono text-xs flex-1'>
<span className="md:hidden">{truncateDid(space.did())}</span>
<span className="hidden md:inline truncate">{space.did()}</span>
</label>
<button
onClick={(e) => {
e.stopPropagation()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: consider creating a new component for this copy button if one doesn't exist already - here's an example with better UX (ie, visual indicator that the copy succeeded): https://github.com/storacha/bluesky-backup-webapp-server/blob/main/src/components/CopyButton.tsx

navigator.clipboard.writeText(space.did())
}}
className='p-1 hover:bg-slate-100 rounded transition-colors'
title='Copy DID'
>
<ClipboardIcon className='w-4 h-4 text-slate-500 hover:text-slate-700' />
</button>
</div>
</div>
</div>
</header>
Expand Down
17 changes: 8 additions & 9 deletions packages/console/src/app/space/[did]/root/[cid]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,18 +105,13 @@ export default function ItemPage ({ params }: PageProps): JSX.Element {
</div>

<div className="flex items-center gap-2">

<button onClick={e => { e.preventDefault(); setRemoveConfirmModalOpen(true) }} className={`inline-block bg-hot-red border border-hot-red hover:bg-white hover:text-hot-red font-epilogue text-white uppercase text-sm px-6 py-2 rounded-full whitespace-nowrap`}>
<TrashIcon className='h-5 w-5 inline-block mr-1 align-middle' style={{marginTop: -4}} /> Remove
</button>

{isPrivateSpace && (
<button
onClick={handleDecrypt}
disabled={!canDecrypt || decryptLoading}
className={`inline-block font-epilogue uppercase text-sm px-6 py-2 rounded-full whitespace-nowrap ${
className={`inline-block font-epilogue uppercase text-sm px-6 py-2 rounded-lg whitespace-nowrap ${
canDecrypt && !decryptLoading
? 'bg-blue-600 border border-blue-600 hover:bg-white hover:text-blue-600 text-white'
? 'bg-white border border-blue-600 hover:bg-blue-600 hover:text-white text-blue-600'
: 'bg-gray-300 border border-gray-300 text-gray-500 cursor-not-allowed'
}`}
>
Expand All @@ -133,6 +128,10 @@ export default function ItemPage ({ params }: PageProps): JSX.Element {
)}
</button>
)}

<button onClick={e => { e.preventDefault(); setRemoveConfirmModalOpen(true) }} className={`inline-block bg-white border border-hot-red hover:bg-hot-red hover:text-white font-epilogue text-hot-red uppercase text-sm px-6 py-2 rounded-lg whitespace-nowrap`}>
<TrashIcon className='h-5 w-5 inline-block mr-1 align-middle' style={{marginTop: -4}} /> Remove
</button>
</div>

{isPrivateSpace && decryptError && (
Expand Down Expand Up @@ -206,10 +205,10 @@ function RemoveConfirmModal ({ isOpen, root, shards, onConfirm, onCancel }: Remo
</p>

<div className='py-2 text-center'>
<button onClick={e => { e.preventDefault(); setConfirmed(true); onConfirm() }} className={`inline-block bg-hot-red-light border border-white hover:bg-white hover:text-hot-red font-epilogue text-hot-red uppercase text-sm px-6 py-2 mr-3 rounded-full whitespace-nowrap ${confirmed ? 'opacity-50' : 'hover:outline'}`} disabled={confirmed}>
<button onClick={e => { e.preventDefault(); setConfirmed(true); onConfirm() }} className={`inline-block bg-hot-red-light border border-white hover:bg-white hover:text-hot-red font-epilogue text-hot-red uppercase text-sm px-6 py-2 mr-3 rounded-lg whitespace-nowrap ${confirmed ? 'opacity-50' : 'hover:outline'}`} disabled={confirmed}>
<TrashIcon className='h-5 w-5 inline-block mr-1 align-middle' style={{marginTop: -4}} /> {confirmed ? 'Removing...' : 'Remove'}
</button>
<button onClick={e => { e.preventDefault(); setConfirmed(false); onCancel() }} className={`inline-block bg-hot-red border border-white hover:bg-white hover:text-hot-red font-epilogue text-white uppercase text-sm px-6 py-2 mr-3 rounded-full whitespace-nowrap ${confirmed ? 'opacity-50' : 'hover:outline'}`} disabled={confirmed}>
<button onClick={e => { e.preventDefault(); setConfirmed(false); onCancel() }} className={`inline-block bg-hot-red border border-white hover:bg-white hover:text-hot-red font-epilogue text-white uppercase text-sm px-6 py-2 mr-3 rounded-lg whitespace-nowrap ${confirmed ? 'opacity-50' : 'hover:outline'}`} disabled={confirmed}>
Cancel
</button>
</div>
Expand Down
Loading