Skip to content

Commit

Permalink
feat: new backend dashboard [wip][ref Codeinwp/neve-pro-addon#2914]
Browse files Browse the repository at this point in the history
  • Loading branch information
abaicus committed Jan 15, 2025
1 parent 8258a95 commit b643134
Show file tree
Hide file tree
Showing 48 changed files with 2,239 additions and 592 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
- uses: actions/cache@v3
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
Expand All @@ -39,7 +39,7 @@ jobs:
run: |
echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Configure Composer cache
uses: actions/cache@v1
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
Expand Down
36 changes: 17 additions & 19 deletions assets/apps/dashboard/src/Components/App.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import Container from '../Layout/Container';
import { fetchOptions } from '../utils/rest';
import Sidebar from './Content/Sidebar/Sidebar';
import Header from './Header';
import Notifications from './Notifications';
import TabsContent from './TabsContent';
import Sidebar from './Sidebar';
import Loading from './Loading';
import SkeletonLoader from './SkeletonLoader';
import Snackbar from './Snackbar';
import Container from '../Layout/Container';
import { fetchOptions } from '../utils/rest';

import { useDispatch, useSelect } from '@wordpress/data';
import { useState, useEffect } from '@wordpress/element';
import Deal from './Deal';
import { useEffect, useState } from '@wordpress/element';
import { tabs } from '../utils/common';
import { TransitionWrapper } from './Common/TransitionWrapper';
import { NEVE_STORE } from '../utils/constants';

const App = () => {
const [loading, setLoading] = useState(true);

const { setSettings, setTab } = useDispatch('neve-dashboard');
const { setSettings, setTab } = useDispatch(NEVE_STORE);

const { toast, currentTab } = useSelect((select) => {
const { getToast, getTab } = select('neve-dashboard');
const { currentTab } = useSelect((select) => {
const { getTab } = select(NEVE_STORE);
return {
toast: getToast(),
currentTab: getTab(),
};
});
Expand All @@ -32,7 +32,7 @@ const App = () => {
}, []);

if (loading) {
return <Loading />;
return <SkeletonLoader />;
}
return (
<div className="antialiased grow flex flex-col gap-6 h-full">
Expand All @@ -42,18 +42,16 @@ const App = () => {
{'starter-sites' !== currentTab && <Notifications />}

<Container className="flex flex-col lg:flex-row gap-6 h-full grow">
<div className="grow">
<TabsContent currentTab={currentTab} setTab={setTab} />
</div>
<div className="grow">{tabs[currentTab].render(setTab)}</div>

{'starter-sites' !== currentTab && (
<div className="shrink-0 lg:w-[435px]">
{!['starter-sites', 'settings'].includes(currentTab) && (
<TransitionWrapper className="shrink-0 lg:w-[435px]">
<Sidebar />
</div>
</TransitionWrapper>
)}
</Container>

{toast && <Snackbar />}
<Snackbar />
</div>
);
};
Expand Down
8 changes: 5 additions & 3 deletions assets/apps/dashboard/src/Components/Common/Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const Button = (props) => {
const {
href,
onClick,
className,
className = '',
isSubmit,
isPrimary,
isSecondary,
Expand All @@ -17,20 +17,22 @@ const Button = (props) => {
} = props;

const classNames = cn([
'flex items-center px-3 py-2 transition-colors duration-150 rounded text-sm border gap-2',
'flex items-center px-3 py-2 transition-colors duration-150 text-sm border gap-2',
{
rounded: !className.includes('rounded'),
'border-transparent bg-blue-600 text-white hover:bg-blue-700 hover:text-white':
isPrimary,
'border-blue-600 text-blue-600 hover:bg-blue-600 hover:text-white':
isSecondary,
'border-transparent text-gray-600 hover:text-gray-900': isLink,
'cursor-not-allowed opacity-50 pointer-events-none': disabled,
'cursor-not-allowed opacity-50': disabled,
},
className,
]);

const passedProps = {
className: classNames,
disabled,
onClick,
};

Expand Down
122 changes: 122 additions & 0 deletions assets/apps/dashboard/src/Components/Common/Multiselect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { useRef, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import cn from 'classnames';
import { Check, ChevronDown } from 'lucide-react';
import { useEffect } from 'react';
const MultiSelect = ({ value, label, disabled, choices = {}, onChange }) => {
const [isOpen, setIsOpen] = useState(false);

const dropdownRef = useRef(null);

const closeDropdown = (e) => {
if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
setIsOpen(false);
}
};

useEffect(() => {
if (isOpen) {
document.addEventListener('click', closeDropdown);
} else {
document.removeEventListener('click', closeDropdown);
}

return () => {
document.removeEventListener('click', closeDropdown);
};
}, [isOpen]);

const handleChange = (optionValue) => {
const nextValues = value.includes(optionValue)
? value.filter((v) => v !== optionValue)
: [...value, optionValue];
onChange(nextValues);
};

return (
<div className="grid gap-1">
{label && (
<span className="text-sm text-gray-600 font-medium">
{label}
</span>
)}
<div className="relative">
<button
onClick={() => !disabled && setIsOpen(!isOpen)}
className={cn(
'relative w-full py-1.5 px-2 text-sm rounded border flex items-center gap-3 min-w-[200px]',
'border-gray-300 hover:border-gray-500',
{
'bg-gray-100': disabled,
}
)}
disabled={disabled}
>
<div className="flex flex-wrap gap-1 max-w-[160px] overflow-hidden">
{Object.entries(value).length === 0 ? (
<span className="text-gray-500">
{__('Select options', 'neve')}...
</span>
) : (
value.map((choice, index) => {
if (!choices[choice]) {
return null;
}

return (
<span
key={index}
className="inline-flex items-center gap-1 bg-blue-100 text-blue-800 rounded px-2 py-0.5 text-xs"
>
{choices[choice]}
</span>
);
})
)}
</div>

<ChevronDown
size={18}
className={`ml-auto transition-transform ${
isOpen ? 'transform rotate-180' : ''
}`}
aria-hidden="true"
/>
</button>

{isOpen && (
<div
ref={dropdownRef}
className="absolute z-10 w-full mt-1 bg-white border rounded shadow-lg"
>
<div className="max-h-60 overflow-y-auto p-1">
{Object.entries(choices).map(
([optionValue, optionLabel]) => (
<button
key={optionValue}
className="flex w-full items-center gap-2 text-sm rounded py-1.5 px-3 hover:bg-blue-100 hover:text-blue-700 cursor-pointer"
onClick={() =>
handleChange(optionValue)
}
>
<div className="w-4 h-4 border bg-white rounded flex items-center justify-center">
{value.includes(optionValue) && (
<Check
size={12}
className="text-blue-600"
/>
)}
</div>
{optionLabel}
</button>
)
)}
</div>
</div>
)}
</div>
</div>
);
};

export default MultiSelect;
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import Tooltip from './Tooltip';
import TransitionInOut from './TransitionInOut';
import { useEffect } from 'react';

const Notification = ({ data, slug }) => {
const Notification = ({ data }) => {
const [hidden, setHidden] = useState(false);
const { text, cta, type, update, url, targetBlank } = data;
const { canInstallPlugins } = neveDash;
Expand Down
2 changes: 1 addition & 1 deletion assets/apps/dashboard/src/Components/Common/Pill.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default ({ children, type = 'primary', className }) => {
const typeClasses = {
primary: 'bg-blue-100 text-blue-700',
secondary: 'bg-gray-100 text-gray-700',
success: 'bg-green-100 text-green-700',
success: 'bg-lime-100 text-lime-700',
error: 'bg-red-100 text-red-700',
warning: 'bg-yellow-100 text-yellow-700',
};
Expand Down
92 changes: 92 additions & 0 deletions assets/apps/dashboard/src/Components/Common/Select.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import cn from 'classnames';
import { LoaderCircle, LucideChevronDown } from 'lucide-react';

import {
Field,
Label,
Listbox,
ListboxButton,
ListboxOption,
ListboxOptions,
} from '@headlessui/react';
import { __ } from '@wordpress/i18n';

export default ({
label,
value,
onChange,
disabled = false,
loading,
choices,
}) => {
return (
<Field className="grid gap-1">
{label && (
<Label className="text-sm text-gray-600 font-medium">
{label}
</Label>
)}
<div className="flex items-center gap-3">
{loading && (
<LoaderCircle size={18} className="animate-spin shrink-0" />
)}
<Listbox
value={value}
onChange={onChange}
disabled={loading || disabled}
>
{({ open }) => (
<>
<ListboxButton
className={cn(
'relative w-full rounded py-1.5 px-2 text-left',
'rounded border border-gray-300 hover:border-gray-500',
'flex items-center gap-3 min-w-[200px]',
{
'bg-gray-100': disabled || loading,
}
)}
>
{choices[value] ||
__('Select an option', 'neve')}

<LucideChevronDown
size={18}
className={cn(
'ml-auto transition-transform',
{
'transform rotate-180': open,
}
)}
aria-hidden="true"
/>
</ListboxButton>

<ListboxOptions
anchor="bottom"
transition
className={cn(
'text-sm font-normal shadow-lg',
'rounded border bg-white p-1 my-1 min-w-[200px]',
'transition duration-100 ease-in data-[leave]:data-[closed]:opacity-0 antialiased'
)}
>
{Object.entries(choices).map(
([optionValue, optionLabel]) => (
<ListboxOption
key={optionValue}
value={optionValue}
className="flex w-full items-center gap-2 text-sm rounded py-1.5 px-3 hover:bg-blue-100 hover:text-blue-700 data-[focus]:bg-blue-100 data-[focus]:text-blue-700 cursor-pointer"
>
{optionLabel}
</ListboxOption>
)
)}
</ListboxOptions>
</>
)}
</Listbox>
</div>
</Field>
);
};
Loading

0 comments on commit b643134

Please sign in to comment.