Skip to content
Merged
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
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"dependencies": {
"@atproto/api": "^0.16.9",
"@tailwindcss/postcss": "^4.1.13",
"clsx": "^2.1.1",
"dayjs": "^1.11.18",
"exifreader": "^4.32.0",
"jimp": "^1.6.0",
Expand Down
13 changes: 7 additions & 6 deletions src/app/drafts/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ import { Callout } from '@/components/ui/callout'
import { LinkButton } from '@/components/ui/forms'
import DraftsProvider from '@/providers/DraftsProvider'
import { Plus } from 'lucide-react'
import TwoColumn from '@/components/ui/TwoColumn'

export default async function Drafts() {
return (
<DraftsProvider>
<div className="flex flex-col-reverse md:flex-row justify-center gap-4">
<main className="*:w-full md:w-1/2">
<TwoColumn reverseStack>
<TwoColumn.Main>
<DraftListFilters />
<DraftPostList />
</main>
<aside className="*:w-full md:w-1/2">
</TwoColumn.Main>
<TwoColumn.Side>
<h1 className="text-2xl font-bold mb-4">Draft List</h1>
<div className="mt-2">
<LinkButton
Expand Down Expand Up @@ -43,8 +44,8 @@ export default async function Drafts() {
</p>
</Callout>
</div>
</aside>
</div>
</TwoColumn.Side>
</TwoColumn>
</DraftsProvider>
)
}
13 changes: 7 additions & 6 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@ import BskyBackupProvider from '@/providers/BskyBackupProvider'
import DraftProvider from '@/providers/DraftsProvider'
import Link from '@/components/ui/link'
import { Callout } from '@/components/ui/callout'
import TwoColumn from '@/components/ui/TwoColumn'

export default async function Home() {
return (
<DraftProvider>
<BskyBackupProvider>
<div className="flex flex-col-reverse md:flex-row justify-center gap-4">
<main className="w-full md:w-1/2">
<TwoColumn reverseStack stackPoint="md">
<TwoColumn.Main>
<BackupToolBar />
<BackupPostList />
</main>
<aside className="w-full md:w-1/2">
</TwoColumn.Main>
<TwoColumn.Side>
<h1 className="text-2xl font-bold mb-4">Backup</h1>
<p className="mb-4">
This tool helps you back up your posts from Bluesky to your local
Expand All @@ -36,8 +37,8 @@ export default async function Home() {
</p>
</Callout>
<Stats />
</aside>
</div>
</TwoColumn.Side>
</TwoColumn>
</BskyBackupProvider>
</DraftProvider>
)
Expand Down
76 changes: 31 additions & 45 deletions src/app/schedules/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export default function SchedulesPage() {
const [isEditing, setIsEditing] = useState(false)
const [editForm, setEditForm] = useState<Partial<Schedule>>({})

const handleCancel = () => {
setIsEditing(false)
}

const handleEdit = (schedule: Schedule) => {
setSelectedSchedule(schedule)
setEditForm(schedule)
Expand All @@ -42,9 +46,9 @@ export default function SchedulesPage() {
<AppDataProvider>
<DraftProvider>
<ScheduleProvider>
<div className="max-w-7xl mx-auto">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Schedule List */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow max-w-xl mx-auto">
{/* Schedule List */}
{!selectedSchedule && !isEditing && (
<ErrorBoundary>
<ScheduleList
onEdit={handleEdit}
Expand All @@ -54,49 +58,31 @@ export default function SchedulesPage() {
setSelectedSchedule={setSelectedSchedule}
/>
</ErrorBoundary>
)}

{selectedSchedule && !isEditing && (
<ErrorBoundary>
<ScheduleDetails
schedule={selectedSchedule!}
onEdit={handleEdit}
onDelete={handleDelete}
onBack={() => setSelectedSchedule(null)}
/>
</ErrorBoundary>
)}

{/* Edit Panel */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow">
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
{isEditing
? selectedSchedule
? 'Edit Schedule'
: 'Create Schedule'
: 'Schedule Details'}
</h2>
</div>
<div className="p-6">
{selectedSchedule || isEditing ? (
<div className="space-y-4">
{isEditing ? (
<ErrorBoundary>
<ScheduleEditForm
schedule={selectedSchedule}
editForm={editForm}
setEditForm={setEditForm}
onSave={handleSave}
setIsEditing={setIsEditing}
/>
</ErrorBoundary>
) : (
<ErrorBoundary>
<ScheduleDetails
schedule={selectedSchedule!}
onEdit={handleEdit}
onDelete={handleDelete}
/>
</ErrorBoundary>
)}
</div>
) : (
<p className="text-gray-500">
Select a schedule to view details
</p>
)}
</div>
</div>
</div>
{/* Edit Panel */}
{isEditing && (
<ErrorBoundary>
<ScheduleEditForm
schedule={selectedSchedule}
editForm={editForm}
setEditForm={setEditForm}
onSave={handleSave}
onCancel={handleCancel}
/>
</ErrorBoundary>
)}
</div>
</ScheduleProvider>
</DraftProvider>
Expand Down
2 changes: 1 addition & 1 deletion src/app/settings/components/SettingsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export default function SettingsForm() {
onSubmit={handleSubmit}
className="space-y-6 bg-color-gray-200 dark:bg-gray-900 p-6 rounded-lg"
>
<div className="grid grid-cols-2 gap-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<SettingsField
label="Bluesky Display Name"
name="bskyDisplayName"
Expand Down
83 changes: 57 additions & 26 deletions src/components/HeaderNav.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,60 @@
import { HEADER_NAV_ITEMS } from "@/config/frontend";
import Link from "next/link";
'use client'
import { HEADER_NAV_ITEMS } from '@/config/frontend'
import Link from 'next/link'
import { Menu } from 'lucide-react'
import { useState } from 'react'
import { Button } from './ui/forms'
import { clsx } from 'clsx'

const HeaderNav = () => (
<div className="flex justify-between border-b border-gray-200 dark:border-gray-700">
<nav>
<ul className="flex gap-4 list-none m-0 p-0 block">
{HEADER_NAV_ITEMS.map((item) => (
<li key={item.href} className="inline-block p-4">
<Link
href={item.href}
className="p-4 text-gray-700 dark:text-gray-200 hover:text-blue-600 dark:hover:text-blue-400 transition-colors"
>
{item.label}
</Link>
</li>
))}
</ul>
</nav>
<div className="flex items-center px-4 border-l border-gray-200 dark:border-gray-700 dark:bg-blue-950 bg-blue-200 dark:text-white text-blue-950">
<span className="text-sm font-bold mr-8">
Local backup, drafts &amp; scheduling for Bluesky.
</span>
<h1 className="text-2xl font-bold">BskyBackup</h1>
const styles = {
navBar:
'relative flex flex-row items-stretch justify-between border-b border-gray-200 dark:border-gray-700',
logoContainer:
'flex items-center p-4 border-l border-gray-200 bg-blue-200 text-blue-950 dark:border-gray-700 dark:bg-blue-950 dark:text-white',
logoText: 'text-2xl font-bold',
navMenu:
'absolute top-full right-0 lg:relative lg:top-auto lg:right-auto lg:block',
navList:
'list-none h-full border-gray-200 border-1 flex flex-col px-10 py-5 bg-[var(--background)] dark:border-gray-800 lg:border-0 lg:items-center lg:flex-row lg:gap-4 lg:m-0 lg:p-0',
navItem: 'p-4 lg:inline-block',
navLink:
'p-4 rounded text-gray-700 hover:text-blue-600 active:text-blue-900 active:bg-blue-500 transition-colors dark:text-gray-200 dark:hover:text-blue-400 dark:active:text-blue-200',
hamburgerButton: 'flex items-center px-4 lg:hidden',
}

const HeaderNav = () => {
const [isMenuOpen, setIsMenuOpen] = useState(false)

return (
<div className={styles.navBar}>
<div className={styles.logoContainer}>
<div className={styles.logoText}>BskyBackup</div>
</div>

{/* Nav: hidden on lg and below unless menu is open */}
<nav className={clsx([isMenuOpen ? 'block' : 'hidden', styles.navMenu])}>
<ul className={styles.navList}>
{HEADER_NAV_ITEMS.map((item) => (
<li key={item.href} className={styles.navItem}>
<Link href={item.href} className={styles.navLink}>
{item.label}
</Link>
</li>
))}
</ul>
</nav>
{/* Hamburger button for mobile */}
<Button
className={styles.hamburgerButton}
aria-label="Toggle navigation menu"
color="primary"
variant="icon"
onClick={() => setIsMenuOpen((open) => !open)}
>
<Menu className="w-6 h-6" />
</Button>
</div>
</div>
);
)
}

export default HeaderNav;
export default HeaderNav
4 changes: 2 additions & 2 deletions src/components/schedules/FrequencyInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -214,11 +214,11 @@ export default function FrequencyInput({
{interval.unit === 'weeks' && (
<div>
<Label>Days of the Week</Label>
<div className="flex flex-wrap gap-2 mt-2">
<div className="grid grid-cols-4 gap-2 mt-2 w-full">
{WEEKDAYS.map((day) => (
<label
key={day}
className="flex items-center space-x-1 text-sm"
className="flex items-center space-x-1 text-md"
>
<input
type="checkbox"
Expand Down
28 changes: 19 additions & 9 deletions src/components/schedules/ScheduleDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ import { Button, Label, LinkButton } from '../ui/forms'
import { displayTime, formatFullDateTime } from '@/helpers/utils'
import { useEffect, useState } from 'react'
import Post from '../Post'
import { ArrowLeftIcon, PencilIcon, TrashIcon, ShareIcon } from 'lucide-react'

export default function ScheduleDetails({
schedule,
onEdit,
onDelete,
onBack,
}: {
schedule: Schedule
onEdit: (schedule: Schedule) => void
onDelete: (id: string) => void
onBack: () => void
}) {
const { deleteSchedule, triggerSchedule, getScheduleLookups } =
useScheduleContext()
Expand Down Expand Up @@ -44,7 +47,7 @@ export default function ScheduleDetails({
}, [schedule.id, getScheduleLookups])

return (
<>
<div className="p-6">
<div className="flex flex-row justify-between gap-4">
<div>
<div className="flex flex-col gap-4">
Expand Down Expand Up @@ -131,27 +134,34 @@ export default function ScheduleDetails({
</div>
</div>
<div className="flex flex-col gap-2">
<Button
onClick={() => onBack()}
color="secondary"
variant="secondary"
>
<ArrowLeftIcon className="w-4 h-4 mr-1" /> Back
</Button>
<Button
onClick={() => onEdit(schedule)}
color="primary"
variant="primary"
>
Edit Schedule
<PencilIcon className="w-4 h-4 mr-1" /> Edit Schedule
</Button>
<Button
onClick={() => schedule.id && handleDelete(schedule.id)}
color="danger"
variant="primary"
>
Delete Schedule
<TrashIcon className="w-4 h-4 mr-1" /> Delete Schedule
</Button>
{lookups?.nextPost && (
<Button
onClick={() => schedule.id && handleTrigger(schedule.id)}
variant="primary"
color="tertiary"
>
Post Now
<ShareIcon className="w-4 h-4 mr-1" /> Post Now
</Button>
)}
{schedule.group && (
Expand All @@ -166,21 +176,21 @@ export default function ScheduleDetails({
</div>
</div>
{lookups && lookups.nextPostDates.length > 0 ? (
<div>
<div className="mt-4">
<Label>Next Post Date</Label>
<div>{formatFullDateTime(lookups.nextPostDates[0])}</div>
</div>
) : (
<div>No next post date available.</div>
<div className="mt-4 font-bold">No next post date available.</div>
)}
{lookups?.nextPost ? (
<div>
<div className="mt-4">
<Label>Next Post</Label>
<Post draftPost={lookups.nextPost} variant="compact" />
</div>
) : (
<div>No upcoming posts.</div>
<div className="mt-4 font-bold">No upcoming posts.</div>
)}
</>
</div>
)
}
Loading
Loading