@_linked/cli 1.4.2 → 1.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/defaults/app-static/src/index-static.tsx +22 -0
  3. package/defaults/app-with-backend/src/App.module.css +32 -0
  4. package/defaults/app-with-backend/src/App.tsx +30 -0
  5. package/defaults/app-with-backend/src/backend.ts +9 -0
  6. package/defaults/app-with-backend/src/components/Error.module.css +9 -0
  7. package/defaults/app-with-backend/src/components/Error.tsx +11 -0
  8. package/defaults/app-with-backend/src/components/PersonOverview.module.css +100 -0
  9. package/defaults/app-with-backend/src/components/PersonOverview.tsx +97 -0
  10. package/defaults/app-with-backend/src/components/PersonOverviewContext.tsx +12 -0
  11. package/defaults/app-with-backend/src/components/PersonPreview.module.css +97 -0
  12. package/defaults/app-with-backend/src/components/PersonPreview.tsx +107 -0
  13. package/defaults/app-with-backend/src/components/Spinner.module.css +17 -0
  14. package/defaults/app-with-backend/src/components/Spinner.tsx +12 -0
  15. package/defaults/app-with-backend/src/index.tsx +34 -0
  16. package/defaults/app-with-backend/src/layout/DefaultLayout.module.css +15 -0
  17. package/defaults/app-with-backend/src/layout/DefaultLayout.tsx +12 -0
  18. package/defaults/app-with-backend/src/layout/Header.module.css +46 -0
  19. package/defaults/app-with-backend/src/layout/Header.tsx +27 -0
  20. package/defaults/app-with-backend/src/linked.frontend.datasets.json +12 -0
  21. package/defaults/app-with-backend/src/linked.frontend.storage.ts +36 -0
  22. package/defaults/app-with-backend/src/package.ts +12 -0
  23. package/defaults/app-with-backend/src/pages/Home.module.css +23 -0
  24. package/defaults/app-with-backend/src/pages/Home.tsx +14 -0
  25. package/defaults/app-with-backend/src/pages/Page1.module.css +18 -0
  26. package/defaults/app-with-backend/src/pages/Page1.tsx +16 -0
  27. package/defaults/app-with-backend/src/pages/PageNotFound.module.css +19 -0
  28. package/defaults/app-with-backend/src/pages/PageNotFound.tsx +14 -0
  29. package/defaults/app-with-backend/src/pages/Signin.module.css +20 -0
  30. package/defaults/app-with-backend/src/pages/Signin.tsx +16 -0
  31. package/defaults/app-with-backend/src/routes.tsx +85 -0
  32. package/defaults/app-with-backend/src/theme.css +91 -0
  33. package/defaults/app-with-backend/src/types.ts +14 -0
  34. package/defaults/app-with-backend/src/utils/lazyWithPreload.tsx +42 -0
  35. package/defaults/app-with-backend/src/utils/preloadRoutes.ts +38 -0
  36. package/defaults/app-with-backend/tsconfig.json +24 -0
  37. package/lib/cjs/cli-deprecated.d.ts +2 -0
  38. package/lib/cjs/cli-methods.d.ts +67 -0
  39. package/lib/cjs/cli.d.ts +3 -0
  40. package/lib/cjs/commands/build-package.d.ts +9 -0
  41. package/lib/cjs/commands/safe-yarn.d.ts +10 -0
  42. package/lib/cjs/commands/setup-publish.d.ts +23 -0
  43. package/lib/cjs/config-webpack-app.d.ts +112 -0
  44. package/lib/cjs/config-webpack.d.ts +55 -0
  45. package/lib/cjs/defineConfig.d.ts +8 -0
  46. package/lib/cjs/index.d.ts +12 -0
  47. package/lib/cjs/interfaces.d.ts +131 -0
  48. package/lib/cjs/loaders/register-css-only.d.ts +2 -0
  49. package/lib/cjs/loaders/register.d.ts +2 -0
  50. package/lib/cjs/metadata.d.ts +1 -0
  51. package/lib/cjs/package.json +1 -1
  52. package/lib/cjs/plugins/check-imports.d.ts +2 -0
  53. package/lib/cjs/plugins/declaration-plugin.d.ts +15 -0
  54. package/lib/cjs/plugins/externalise-modules.d.ts +7 -0
  55. package/lib/cjs/plugins/lincd-tailwind-sources.d.ts +3 -0
  56. package/lib/cjs/plugins/watch-run.d.ts +5 -0
  57. package/lib/cjs/tailwind.config.d.ts +8 -0
  58. package/lib/cjs/utils.d.ts +77 -0
  59. package/lib/esm/cli-deprecated.d.ts +2 -0
  60. package/lib/esm/cli-methods.d.ts +67 -0
  61. package/lib/esm/cli.d.ts +3 -0
  62. package/lib/esm/commands/build-package.d.ts +9 -0
  63. package/lib/esm/commands/safe-yarn.d.ts +10 -0
  64. package/lib/esm/commands/setup-publish.d.ts +23 -0
  65. package/lib/esm/config-webpack-app.d.ts +112 -0
  66. package/lib/esm/config-webpack.d.ts +55 -0
  67. package/lib/esm/defineConfig.d.ts +8 -0
  68. package/lib/esm/index.d.ts +12 -0
  69. package/lib/esm/interfaces.d.ts +131 -0
  70. package/lib/esm/launch.d.ts +3 -0
  71. package/lib/esm/loaders/register-css-only.d.ts +2 -0
  72. package/lib/esm/loaders/register.d.ts +2 -0
  73. package/lib/esm/metadata.d.ts +1 -0
  74. package/lib/esm/package.json +1 -1
  75. package/lib/esm/plugins/check-imports.d.ts +2 -0
  76. package/lib/esm/plugins/declaration-plugin.d.ts +15 -0
  77. package/lib/esm/plugins/externalise-modules.d.ts +7 -0
  78. package/lib/esm/plugins/lincd-tailwind-sources.d.ts +3 -0
  79. package/lib/esm/plugins/watch-run.d.ts +5 -0
  80. package/lib/esm/tailwind.config.d.ts +8 -0
  81. package/lib/esm/utils.d.ts +77 -0
  82. package/package.json +1 -1
  83. package/tests/e2e/create-app.spec.ts +157 -0
  84. package/tests/unit/safeYarn.test.ts +55 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.4.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [#28](https://github.com/linked-cm/cli/pull/28) [`1d2338c`](https://github.com/linked-cm/cli/commit/1d2338c09a38e44caec2aa69a72b3feda7d098bf) Thanks [@flyon](https://github.com/flyon)! - `.npmignore` was excluding the starter template's `defaults/app-with-backend/src/` (and `scripts/`) because the `src` pattern matched **anywhere** in the tree, overriding the `!defaults/**/*` re-include. The published tarball was missing all the template's TypeScript source files, so `linked create-app` was producing apps without `src/`.
8
+
9
+ Anchor the cli's own-source ignore rules to the package root (`/src`, `/*.ts`, `/tsconfig.json`) and add an explicit `!defaults/**` re-include after.
10
+
3
11
  ## 1.4.2
4
12
 
5
13
  ### Patch Changes
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import {BrowserRouter} from 'react-router-dom';
4
+ import App from './App';
5
+ import {AppContextProvider} from 'lincd-server-utils/lib/components/AppContext';
6
+
7
+ //import the storage & file configuration for the frontend
8
+ import './config-frontend';
9
+
10
+ const root = ReactDOM.createRoot(
11
+ document.getElementById('root') as HTMLElement,
12
+ );
13
+
14
+ root.render(
15
+ <React.StrictMode>
16
+ <BrowserRouter>
17
+ <AppContextProvider isNativeApp={true}>
18
+ <App />
19
+ </AppContextProvider>
20
+ </BrowserRouter>
21
+ </React.StrictMode>,
22
+ );
@@ -0,0 +1,32 @@
1
+ @import '@_linked/css/package.css';
2
+
3
+ :global(body) {
4
+ font-family: var(--font-sans);
5
+ background: var(--bg-page);
6
+ color: var(--color-gray-900);
7
+ margin: 0;
8
+ }
9
+
10
+ .App {
11
+ min-height: 100vh;
12
+ }
13
+
14
+ :global(h1) {
15
+ font-size: 1.75rem;
16
+ font-weight: 600;
17
+ margin: 0;
18
+ }
19
+
20
+ :global(h2) {
21
+ font-size: 1.25rem;
22
+ font-weight: 600;
23
+ margin: 0 0 --spacing(3);
24
+ }
25
+
26
+ :global(a) {
27
+ color: var(--color-primary-600);
28
+ }
29
+
30
+ :global(a:hover) {
31
+ color: var(--color-primary-700);
32
+ }
@@ -0,0 +1,30 @@
1
+ import './theme.css'; //needs to be the first import before importing other components
2
+ import React, { Suspense } from 'react';
3
+ import { ErrorBoundary } from 'react-error-boundary';
4
+ import { Spinner } from './components/Spinner';
5
+ import { Error } from './components/Error';
6
+ import { AppRoot } from '@_linked/server-utils/components/AppRoot';
7
+ import { Head } from '@_linked/server-utils/components/Head';
8
+ import { Body } from '@_linked/server-utils/components/Body';
9
+ import AppRoutes, { ROUTES } from './routes';
10
+ import { ProvideAuth } from '@_linked/auth/hooks/useAuth';
11
+ import style from './App.module.css'; //import any .module.css file like this and access the classnames from the style object
12
+
13
+ export default function App() {
14
+ return (
15
+ <AppRoot>
16
+ <Head>
17
+ {/* Add tags to html <head> here, for example, a font <link href='https://fonts.someapi.com/...' />*/}
18
+ </Head>
19
+ <Body routes={ROUTES} pageStyles={style} className={style.App}>
20
+ <Suspense fallback={<Spinner />}>
21
+ <ErrorBoundary FallbackComponent={Error}>
22
+ <ProvideAuth>
23
+ <AppRoutes />
24
+ </ProvideAuth>
25
+ </ErrorBoundary>
26
+ </Suspense>
27
+ </Body>
28
+ </AppRoot>
29
+ );
30
+ }
@@ -0,0 +1,9 @@
1
+ //import all ShapeProviders here or define a generic BackendProvider, see https://linked.cm
2
+
3
+ import { BackendProvider } from '@_linked/server-utils/utils/BackendProvider';
4
+
5
+ export class Backend extends BackendProvider {
6
+ constructor(server, linkedServer) {
7
+ super(server, linkedServer);
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ .error {
2
+ color: white;
3
+ margin: 2rem;
4
+
5
+ pre {
6
+ white-space: pre-wrap;
7
+ padding: 1rem;
8
+ }
9
+ }
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import style from './Error.module.css';
3
+
4
+ export function Error({ error }) {
5
+ return (
6
+ <div className={style.error}>
7
+ <h1>Application Error</h1>
8
+ <pre>{error.stack}</pre>
9
+ </div>
10
+ );
11
+ }
@@ -0,0 +1,100 @@
1
+ @import '@_linked/css/package.css';
2
+
3
+ .Root {
4
+ background: var(--bg-card);
5
+ border-radius: var(--radius-card);
6
+ box-shadow: var(--shadow-card);
7
+ padding: --spacing(6);
8
+ }
9
+
10
+ .Intro {
11
+ color: var(--color-gray-600);
12
+ font-size: 0.9rem;
13
+ line-height: 1.5;
14
+ margin: 0 0 --spacing(5);
15
+ }
16
+
17
+ .Intro code {
18
+ background: var(--bg-section);
19
+ padding: 0 --spacing(1);
20
+ border-radius: var(--radius-element);
21
+ font-family: var(--font-mono);
22
+ font-size: 0.85em;
23
+ }
24
+
25
+ .Empty {
26
+ color: var(--color-gray-500);
27
+ font-style: italic;
28
+ padding: --spacing(4) 0;
29
+ margin: 0;
30
+ }
31
+
32
+ .Loading {
33
+ color: var(--color-gray-500);
34
+ padding: --spacing(4) 0;
35
+ margin: 0;
36
+ }
37
+
38
+ .List {
39
+ list-style: none;
40
+ padding: 0;
41
+ margin: 0 0 --spacing(5);
42
+ }
43
+
44
+ .List > li {
45
+ padding: --spacing(3) 0;
46
+ border-bottom: 1px solid var(--color-gray-200);
47
+ }
48
+
49
+ .List > li:first-child {
50
+ padding-top: 0;
51
+ }
52
+
53
+ .List > li:last-child {
54
+ border-bottom: none;
55
+ }
56
+
57
+ .Form {
58
+ display: flex;
59
+ gap: --spacing(2);
60
+ flex-wrap: wrap;
61
+ padding-top: --spacing(4);
62
+ border-top: 1px solid var(--color-gray-200);
63
+ }
64
+
65
+ .Form input {
66
+ flex: 1 1 140px;
67
+ padding: --spacing(2) --spacing(3);
68
+ border: 1px solid var(--color-gray-300);
69
+ border-radius: var(--radius-element);
70
+ background: var(--bg-card);
71
+ color: var(--color-gray-900);
72
+ font: inherit;
73
+ }
74
+
75
+ .Form input:focus {
76
+ outline: none;
77
+ border-color: var(--color-primary-500);
78
+ box-shadow: 0 0 0 2px var(--color-primary-200);
79
+ }
80
+
81
+ .Form button {
82
+ padding: --spacing(2) --spacing(5);
83
+ background: var(--color-primary-500);
84
+ color: var(--color-white);
85
+ border: none;
86
+ border-radius: var(--radius-element);
87
+ font: inherit;
88
+ font-weight: 500;
89
+ cursor: pointer;
90
+ transition: background 120ms ease;
91
+ }
92
+
93
+ .Form button:hover:not(:disabled) {
94
+ background: var(--color-primary-600);
95
+ }
96
+
97
+ .Form button:disabled {
98
+ opacity: 0.6;
99
+ cursor: not-allowed;
100
+ }
@@ -0,0 +1,97 @@
1
+ // Example PersonOverview — demonstrates @_linked/react's data binding:
2
+ // linkedSetComponent for the list (auto-runs the query, injects results and
3
+ // an injected _refresh); linkedComponent for each row (see PersonPreview).
4
+ // After Person.create the form calls _refresh() to re-run the list query.
5
+ //
6
+ // Replace or extend this with your own shapes (see https://linked.cm).
7
+ import React, { useCallback, useRef, useState } from 'react';
8
+ import { linkedSetComponent } from '@_linked/react';
9
+ import { Person } from '@_linked/schema/shapes/Person';
10
+ import { PersonPreview } from './PersonPreview';
11
+ import { PersonListRefreshProvider } from './PersonOverviewContext';
12
+ import style from './PersonOverview.module.css';
13
+
14
+ const PersonList = linkedSetComponent(
15
+ Person.select((p) => [p.givenName, p.familyName]),
16
+ ({ linkedData = [], _refresh, refreshRef }: any) => {
17
+ // Forward the list's _refresh up via a ref so the sibling form +
18
+ // child rows can trigger a re-fetch through context.
19
+ if (refreshRef) {
20
+ refreshRef.current = _refresh;
21
+ }
22
+
23
+ if (linkedData.length === 0) {
24
+ return (
25
+ <p className={style.Empty}>No people yet. Add one below.</p>
26
+ );
27
+ }
28
+
29
+ return (
30
+ <ul className={style.List} data-testid="person-list">
31
+ {linkedData.map((p: { id: string }) => (
32
+ <li key={p.id}>
33
+ <PersonPreview of={{ id: p.id }} />
34
+ </li>
35
+ ))}
36
+ </ul>
37
+ );
38
+ },
39
+ );
40
+
41
+ function PersonAddForm({ onAdded }: { onAdded: () => void }) {
42
+ const [given, setGiven] = useState('');
43
+ const [family, setFamily] = useState('');
44
+ const [busy, setBusy] = useState(false);
45
+
46
+ async function submit(e: React.FormEvent) {
47
+ e.preventDefault();
48
+ if (!given && !family) return;
49
+ setBusy(true);
50
+ try {
51
+ await Person.create({ givenName: given, familyName: family });
52
+ setGiven('');
53
+ setFamily('');
54
+ onAdded();
55
+ } finally {
56
+ setBusy(false);
57
+ }
58
+ }
59
+
60
+ return (
61
+ <form className={style.Form} onSubmit={submit}>
62
+ <input
63
+ value={given}
64
+ placeholder="First name"
65
+ onChange={(e) => setGiven(e.target.value)}
66
+ />
67
+ <input
68
+ value={family}
69
+ placeholder="Last name"
70
+ onChange={(e) => setFamily(e.target.value)}
71
+ />
72
+ <button type="submit" disabled={busy}>
73
+ Add
74
+ </button>
75
+ </form>
76
+ );
77
+ }
78
+
79
+ export function PersonOverview() {
80
+ const refreshRef = useRef<() => void>(() => {});
81
+ const refresh = useCallback(() => refreshRef.current(), []);
82
+
83
+ return (
84
+ <PersonListRefreshProvider value={refresh}>
85
+ <div className={style.Root} data-testid="person-overview">
86
+ <h2>People</h2>
87
+ <p className={style.Intro}>
88
+ Example code using the <code>@_linked</code> query DSL against
89
+ your local Fuseki dataset. Edit{' '}
90
+ <code>src/components/PersonOverview.tsx</code> to extend it.
91
+ </p>
92
+ <PersonList refreshRef={refreshRef} />
93
+ <PersonAddForm onAdded={refresh} />
94
+ </div>
95
+ </PersonListRefreshProvider>
96
+ );
97
+ }
@@ -0,0 +1,12 @@
1
+ // Small context that lets PersonPreview rows trigger a list refresh on
2
+ // their parent PersonOverview without prop-drilling through the
3
+ // linkedSetComponent's wrapped renderer.
4
+ import React, { useContext } from 'react';
5
+
6
+ const PersonListRefreshContext = React.createContext<() => void>(() => {});
7
+
8
+ export const PersonListRefreshProvider = PersonListRefreshContext.Provider;
9
+
10
+ export function usePersonListRefresh(): () => void {
11
+ return useContext(PersonListRefreshContext);
12
+ }
@@ -0,0 +1,97 @@
1
+ @import '@_linked/css/package.css';
2
+
3
+ .Row {
4
+ display: flex;
5
+ align-items: center;
6
+ justify-content: space-between;
7
+ gap: --spacing(2);
8
+ flex-wrap: wrap;
9
+ }
10
+
11
+ .Name {
12
+ font-weight: 500;
13
+ color: var(--color-gray-900);
14
+ }
15
+
16
+ .Actions {
17
+ display: flex;
18
+ gap: --spacing(2);
19
+ }
20
+
21
+ .EditButton,
22
+ .SaveButton,
23
+ .CancelButton,
24
+ .DeleteButton {
25
+ padding: --spacing(1) --spacing(3);
26
+ font: inherit;
27
+ font-size: 0.85rem;
28
+ border-radius: var(--radius-element);
29
+ border: 1px solid transparent;
30
+ cursor: pointer;
31
+ transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
32
+ }
33
+
34
+ .EditButton,
35
+ .CancelButton {
36
+ background: transparent;
37
+ color: var(--color-gray-700);
38
+ border-color: var(--color-gray-300);
39
+ }
40
+
41
+ .EditButton:hover:not(:disabled),
42
+ .CancelButton:hover:not(:disabled) {
43
+ background: var(--color-gray-100);
44
+ color: var(--color-gray-900);
45
+ }
46
+
47
+ .SaveButton {
48
+ background: var(--color-primary-500);
49
+ color: var(--color-white);
50
+ }
51
+
52
+ .SaveButton:hover:not(:disabled) {
53
+ background: var(--color-primary-600);
54
+ }
55
+
56
+ .DeleteButton {
57
+ background: transparent;
58
+ color: var(--color-red-600);
59
+ border-color: transparent;
60
+ }
61
+
62
+ .DeleteButton:hover:not(:disabled) {
63
+ color: var(--color-red-700);
64
+ background: var(--color-red-50);
65
+ }
66
+
67
+ .EditButton:disabled,
68
+ .SaveButton:disabled,
69
+ .CancelButton:disabled,
70
+ .DeleteButton:disabled {
71
+ opacity: 0.5;
72
+ cursor: not-allowed;
73
+ }
74
+
75
+ .EditRow {
76
+ display: flex;
77
+ align-items: center;
78
+ gap: --spacing(2);
79
+ flex-wrap: wrap;
80
+ width: 100%;
81
+ }
82
+
83
+ .EditRow input {
84
+ flex: 1 1 140px;
85
+ padding: --spacing(2) --spacing(3);
86
+ border: 1px solid var(--color-gray-300);
87
+ border-radius: var(--radius-element);
88
+ background: var(--bg-card);
89
+ color: var(--color-gray-900);
90
+ font: inherit;
91
+ }
92
+
93
+ .EditRow input:focus {
94
+ outline: none;
95
+ border-color: var(--color-primary-500);
96
+ box-shadow: 0 0 0 2px var(--color-primary-200);
97
+ }
@@ -0,0 +1,107 @@
1
+ // Example PersonPreview — single-Person row. Demonstrates @_linked/react's
2
+ // linkedComponent: the wrapper runs the per-row query and injects query
3
+ // result keys + `source` + `_refresh` into the render function. _refresh
4
+ // patches local query state for optimistic UI; on delete, the parent
5
+ // list re-runs via the PersonListRefresh context.
6
+ import React, { useState } from 'react';
7
+ import { linkedComponent } from '@_linked/react';
8
+ import { Person } from '@_linked/schema/shapes/Person';
9
+ import { usePersonListRefresh } from './PersonOverviewContext';
10
+ import style from './PersonPreview.module.css';
11
+
12
+ export const PersonPreview = linkedComponent(
13
+ Person.select((p) => [p.givenName, p.familyName]),
14
+ ({ givenName, familyName, source, _refresh }) => {
15
+ const refreshList = usePersonListRefresh();
16
+ const [editing, setEditing] = useState(false);
17
+ const [draftGiven, setDraftGiven] = useState(givenName ?? '');
18
+ const [draftFamily, setDraftFamily] = useState(familyName ?? '');
19
+ const [busy, setBusy] = useState(false);
20
+
21
+ async function save() {
22
+ setBusy(true);
23
+ try {
24
+ await Person.update({
25
+ givenName: draftGiven,
26
+ familyName: draftFamily,
27
+ }).for(source);
28
+ // Optimistic patch — no extra network round-trip needed.
29
+ _refresh({ givenName: draftGiven, familyName: draftFamily });
30
+ setEditing(false);
31
+ } finally {
32
+ setBusy(false);
33
+ }
34
+ }
35
+
36
+ async function remove() {
37
+ setBusy(true);
38
+ try {
39
+ await Person.delete(source);
40
+ refreshList();
41
+ } finally {
42
+ setBusy(false);
43
+ }
44
+ }
45
+
46
+ function startEdit() {
47
+ setDraftGiven(givenName ?? '');
48
+ setDraftFamily(familyName ?? '');
49
+ setEditing(true);
50
+ }
51
+
52
+ if (editing) {
53
+ return (
54
+ <span className={style.EditRow} data-testid="person-edit">
55
+ <input
56
+ value={draftGiven}
57
+ onChange={(e) => setDraftGiven(e.target.value)}
58
+ aria-label="First name"
59
+ />
60
+ <input
61
+ value={draftFamily}
62
+ onChange={(e) => setDraftFamily(e.target.value)}
63
+ aria-label="Last name"
64
+ />
65
+ <button
66
+ className={style.SaveButton}
67
+ onClick={save}
68
+ disabled={busy}
69
+ >
70
+ Save
71
+ </button>
72
+ <button
73
+ className={style.CancelButton}
74
+ onClick={() => setEditing(false)}
75
+ disabled={busy}
76
+ >
77
+ Cancel
78
+ </button>
79
+ </span>
80
+ );
81
+ }
82
+
83
+ return (
84
+ <span className={style.Row} data-testid="person-row">
85
+ <span className={style.Name} data-testid="person-name">
86
+ {givenName} {familyName}
87
+ </span>
88
+ <span className={style.Actions}>
89
+ <button
90
+ className={style.EditButton}
91
+ onClick={startEdit}
92
+ disabled={busy}
93
+ >
94
+ Edit
95
+ </button>
96
+ <button
97
+ className={style.DeleteButton}
98
+ onClick={remove}
99
+ disabled={busy}
100
+ >
101
+ Delete
102
+ </button>
103
+ </span>
104
+ </span>
105
+ );
106
+ },
107
+ );
@@ -0,0 +1,17 @@
1
+ .spinner.active {
2
+ border: 4px solid transparent;
3
+ border-top: 4px solid #474545;
4
+ border-radius: 50%;
5
+ width: 16px;
6
+ height: 16px;
7
+ animation: spin 1s linear infinite;
8
+ }
9
+
10
+ @keyframes spin {
11
+ 0% {
12
+ transform: rotate(0deg);
13
+ }
14
+ 100% {
15
+ transform: rotate(360deg);
16
+ }
17
+ }
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import style from './Spinner.module.css';
3
+
4
+ export function Spinner({ active = true }) {
5
+ return (
6
+ <div
7
+ className={[style.spinner, active && style.active].join(' ')}
8
+ role="progressbar"
9
+ aria-busy={active ? 'true' : 'false'}
10
+ />
11
+ );
12
+ }
@@ -0,0 +1,34 @@
1
+ import { AppContextProvider } from '@_linked/server-utils/components/AppContext';
2
+ import { initFrontend } from '@_linked/server-utils/utils/Frontend';
3
+ import React from 'react';
4
+ import { hydrateRoot } from 'react-dom/client';
5
+ import { BrowserRouter } from 'react-router-dom';
6
+ import App from './App';
7
+ import { preloadMatchedRoute } from './utils/preloadRoutes';
8
+
9
+ //import the storage & file configuration for the frontend
10
+ import './linked.frontend.storage';
11
+
12
+ //to avoid errors with react-refresh-webpack-plugin
13
+ window['$RefreshReg$'] = () => {};
14
+ window['$RefreshSig$'] = () => () => {};
15
+
16
+ initFrontend().then(async () => {
17
+ // Preload matched route before hydration to avoid Suspense mismatch
18
+ await preloadMatchedRoute();
19
+
20
+ hydrateRoot(
21
+ document.getElementById('root'), // Target the #root div for hydration
22
+ <React.StrictMode>
23
+ <BrowserRouter>
24
+ <AppContextProvider
25
+ assets={window['assetManifest']}
26
+ requestLD={document.getElementById('request-ld')?.innerText}
27
+ requestObject={JSON.parse(document.getElementById('request-json')?.innerText || '{}')}
28
+ >
29
+ <App />
30
+ </AppContextProvider>
31
+ </BrowserRouter>
32
+ </React.StrictMode>,
33
+ );
34
+ });
@@ -0,0 +1,15 @@
1
+ @import '@_linked/css/package.css';
2
+
3
+ .main {
4
+ font-family: var(--font-sans);
5
+ max-width: 960px;
6
+ margin: 0 auto;
7
+ padding: --spacing(6) --spacing(4);
8
+
9
+ @media (max-width: 1024px) {
10
+ max-width: 90%;
11
+ }
12
+ @media (max-width: 768px) {
13
+ padding: --spacing(4) --spacing(3);
14
+ }
15
+ }
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import { Header } from './Header';
3
+ import style from './DefaultLayout.module.css';
4
+
5
+ export function DefaultLayout({ children }) {
6
+ return (
7
+ <main className={style.main}>
8
+ <Header />
9
+ {children}
10
+ </main>
11
+ );
12
+ }
@@ -0,0 +1,46 @@
1
+ @import '@_linked/css/package.css';
2
+
3
+ .header {
4
+ display: flex;
5
+ align-items: center;
6
+ justify-content: space-between;
7
+ gap: --spacing(4);
8
+ padding: --spacing(4) 0 --spacing(6);
9
+ flex-wrap: wrap;
10
+ }
11
+
12
+ .header h1 {
13
+ color: var(--color-primary-700);
14
+ margin: 0;
15
+ }
16
+
17
+ .menu {
18
+ display: flex;
19
+ gap: --spacing(1);
20
+ flex-wrap: wrap;
21
+ }
22
+
23
+ .menu > a {
24
+ font-weight: 500;
25
+ font-size: 0.95rem;
26
+ color: var(--color-gray-700);
27
+ padding: --spacing(2) --spacing(3);
28
+ border-radius: var(--radius-element);
29
+ text-decoration: none;
30
+ transition: background 120ms ease, color 120ms ease;
31
+ }
32
+
33
+ .menu > a:hover {
34
+ background: var(--color-gray-100);
35
+ color: var(--color-gray-900);
36
+ }
37
+
38
+ .menu > a.active {
39
+ background: var(--color-primary-500);
40
+ color: var(--color-white);
41
+ }
42
+
43
+ .menu > a.active:hover {
44
+ background: var(--color-primary-600);
45
+ color: var(--color-white);
46
+ }