From 55b9b192c945fb3c6023fc069baa2d18efb31145 Mon Sep 17 00:00:00 2001 From: KingSand08 Date: Wed, 18 Jun 2025 01:27:45 -0700 Subject: [PATCH 1/3] Resolved issue #1709 - Reordered inputs (alias, toggle for alias, then original url) - toggle default is toggled off (false) - changed the url css to be light-mode compatible (originally only dark-mode) --- src/Pages/URLShortener/URLShortener.js | 450 ++++++++++++++++--------- 1 file changed, 285 insertions(+), 165 deletions(-) diff --git a/src/Pages/URLShortener/URLShortener.js b/src/Pages/URLShortener/URLShortener.js index a93cbbc72..0b1e73b10 100644 --- a/src/Pages/URLShortener/URLShortener.js +++ b/src/Pages/URLShortener/URLShortener.js @@ -1,6 +1,5 @@ import React, { useEffect, useState } from 'react'; - import { getAllUrls, createUrl, deleteUrl } from '../../APIFunctions/Cleezy'; import { trashcanSymbol } from '../Overview/SVG'; import ConfirmationModal from '../../Components/DecisionModal/ConfirmationModal.js'; @@ -10,7 +9,7 @@ export default function URLShortenerPage(props) { const [url, setUrl] = useState(''); const [invalidUrl, setInvalidUrl] = useState(); const [showUrlInput, setShowUrlInput] = useState(false); - const [useGeneratedAlias, setUseGeneratedAlias] = useState(true); + const [useGeneratedAlias, setUseGeneratedAlias] = useState(false); const [alias, setAlias] = useState(''); const [allUrls, setAllUrls] = useState([]); const [loading, setLoading] = useState(true); @@ -28,14 +27,20 @@ export default function URLShortenerPage(props) { const [currentSortColumn, setCurrentSortColumn] = useState(null); const [currentSortOrder, setCurrentSortOrder] = useState(null); - const INPUT_CLASS = 'indent-2 block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 text-gray'; + const INPUT_CLASS = + 'indent-2 block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 text-gray'; const LABEL_CLASS = 'block text-sm font-medium leading-6 text-gray-300'; /** * Cleezy page is disabled by default since you have to run the Cleezy server * separately. To enable, go to config.json and set ENABLED under Cleezy to true */ - async function getCleezyUrls(page, searchQuery, currentSortColumn, currentSortOrder) { + async function getCleezyUrls( + page, + searchQuery, + currentSortColumn, + currentSortOrder + ) { setLoading(true); const sortColumn = currentSortColumn ?? 'created_at'; const sortOrder = currentSortOrder ?? 'DESC'; @@ -44,7 +49,7 @@ export default function URLShortenerPage(props) { page: page, search: searchQuery, sortColumn: sortColumn, - sortOrder: sortOrder + sortOrder: sortOrder, }); setIsCleezyDisabled(!!urlsFromDb.responseData.disabled); if (urlsFromDb.error) { @@ -70,7 +75,9 @@ export default function URLShortenerPage(props) { setAlias(''); setShowUrlInput(false); setTotal(total + 1); - setSuccessMessage(`Sucessfully created shortened link ${response.responseData.link}`); + setSuccessMessage( + `Sucessfully created shortened link ${response.responseData.link}` + ); setTimeout(() => { setSuccessMessage(null); }, 5000); @@ -111,10 +118,9 @@ export default function URLShortenerPage(props) { } async function handleDeleteUrl(alias) { - const response = await deleteUrl(alias, props.user.token); if (!response.error) { - setAllUrls(allUrls.filter(url => url.alias !== alias)); + setAllUrls(allUrls.filter((url) => url.alias !== alias)); setTotal(total - 1); } } @@ -123,7 +129,7 @@ export default function URLShortenerPage(props) { if (columnName === null) { return; } - if(currentSortColumn === columnName) { + if (currentSortColumn === columnName) { if (currentSortOrder === 'ASC') { setCurrentSortOrder('DESC'); } else if (currentSortOrder === 'DESC') { @@ -142,7 +148,6 @@ export default function URLShortenerPage(props) { return 'hidden'; } - useEffect(() => { if (useGeneratedAlias) { setAlias(''); @@ -175,19 +180,34 @@ export default function URLShortenerPage(props) { function maybeRenderErrorAlert() { if (invalidUrl || aliasTaken || invalidSearch) { return ( -
- - { errorAlertMessage } +
+ + + + {errorAlertMessage}
); } } function maybeRenderPagination() { - const amountOfUrlsOnPage = Math.min((page + 1) * rowsPerPage, allUrls.length); + const amountOfUrlsOnPage = Math.min( + (page + 1) * rowsPerPage, + allUrls.length + ); const pageOffset = page * rowsPerPage; const endingElementNumber = amountOfUrlsOnPage + pageOffset; - const startingElementNumber = (page * rowsPerPage) + 1; + const startingElementNumber = page * rowsPerPage + 1; if (!allUrls.length) { return <>; } else { @@ -195,14 +215,22 @@ export default function URLShortenerPage(props) {
); + ); } function maybeRenderSearch() { return ( - <>
- Type a search, followed by the enter key -
{ - if (event.key === 'Enter') { - event.preventDefault(); - // instead of calling the backend directory, set - // the page we are on to zero if the current page - // we are on isn't the first page (value of 0). - // by doing this, the useEffect will call the backend - // for us with the correct page and query. - if (page) { - setPage(0); - } else { - maybeSubmitSearch(); + <> +
+ + Type a search, followed by the enter key + +
+ { + if (event.key === 'Enter') { + event.preventDefault(); + // instead of calling the backend directory, set + // the page we are on to zero if the current page + // we are on isn't the first page (value of 0). + // by doing this, the useEffect will call the backend + // for us with the correct page and query. + if (page) { + setPage(0); + } else { + maybeSubmitSearch(); + } } - } - } } - onChange={event => { - setSearchQuery(event.target.value); - } } /> + }} + onChange={(event) => { + setSearchQuery(event.target.value); + }} + /> + ); } @@ -348,22 +392,21 @@ export default function URLShortenerPage(props) {

To enable:

    -
  1. Modify
    api/config/config.json
    to include - - 'Cleezy': { - 'ENABLED': true - } - -
  2. - Clone Cleezy locally and follow the steps in - {' '} - the readme - to run locally. + Modify
    api/config/config.json
    to include + 'Cleezy': { 'ENABLED': true }
  3. - Run Clark and log in with an Admin account. + Clone Cleezy locally and follow the steps in{' '} + + the readme + {' '} + to run locally.
  4. +
  5. Run Clark and log in with an Admin account.
); @@ -381,64 +424,117 @@ export default function URLShortenerPage(props) { } return ( - // the below input layout is taken from - // https://tailwindui.com/components/application-ui/forms/form-layouts -
- + { - handleDeleteUrl(urlToDelete.alias); - setToggleDelete(false); - }, - handleCancel: () => { - setToggleDelete(false); - }, - open: toggleDelete - } - } /> + confirmText: `Yes, delete ${urlToDelete.alias}`, + confirmClassAddons: 'bg-red-600 hover:bg-red-500', + handleConfirmation: () => { + handleDeleteUrl(urlToDelete.alias); + setToggleDelete(false); + }, + handleCancel: () => { + setToggleDelete(false); + }, + open: toggleDelete, + }} + />
{maybeRenderErrorAlert()} - {successMessage && + {successMessage && (
-
- +
+ + + {successMessage}
- } + )}
{renderUrlButtonOrForm()}
-
- {maybeRenderSearch()} -
+
{maybeRenderSearch()}
{[ - { title: 'URL', className: 'text-base text-white/70', columnName: 'alias' }, - { title: 'Created At', className: 'text-base text-white/70 hidden text-center sm:table-cell', columnName: 'created_at' }, - { title: 'Times Used', className: 'text-base text-white/70 text-center', columnName: 'used' }, - { title: 'Delete', className: 'text-base text-white/70 text-center' } + { + title: 'URL', + className: 'text-base text-white/70', + columnName: 'alias', + }, + { + title: 'Created At', + className: + 'text-base text-white/70 hidden text-center sm:table-cell', + columnName: 'created_at', + }, + { + title: 'Times Used', + className: 'text-base text-white/70 text-center', + columnName: 'used', + }, + { + title: 'Delete', + className: 'text-base text-white/70 text-center', + }, ].map(({ title, className, columnName = null }) => ( - @@ -449,16 +545,36 @@ export default function URLShortenerPage(props) { {allUrls.map((url, index) => { return ( - +
-
- - - + +
+ + + - - + +
- + {url.alias} -

{url.url.length > 60 ? url.url.slice(0, 50) + '...' : url.url}

+

+ {url.url.length > 60 + ? url.url.slice(0, 50) + '...' + : url.url} +

- {new Date(url.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}
+ {new Date(url.created_at).toLocaleDateString( + 'en-US', + { + month: 'short', + day: 'numeric', + year: 'numeric', + } + )}{' '} +
@@ -468,10 +584,12 @@ export default function URLShortenerPage(props) {
@@ -483,7 +601,9 @@ export default function URLShortenerPage(props) {
{allUrls.length === 0 && (
-

No results found!

+

+ No results found! +

)} {maybeRenderPagination()} From 07b99b6d53f0d48ada194359420975491d3b1ad6 Mon Sep 17 00:00:00 2001 From: KingSand08 Date: Thu, 19 Jun 2025 22:20:45 -0700 Subject: [PATCH 2/3] Fixed auto-corrections as per requested - Created temp file to store original changes I made, as requested - Removed auto-corrections from file (only adding what was originally requested) --- src/Pages/URLShortener/2DPrinting.js | 616 +++++++++++++++++++++++++ src/Pages/URLShortener/URLShortener.js | 449 +++++++----------- 2 files changed, 781 insertions(+), 284 deletions(-) create mode 100644 src/Pages/URLShortener/2DPrinting.js diff --git a/src/Pages/URLShortener/2DPrinting.js b/src/Pages/URLShortener/2DPrinting.js new file mode 100644 index 000000000..3c95b0081 --- /dev/null +++ b/src/Pages/URLShortener/2DPrinting.js @@ -0,0 +1,616 @@ +import React, { useEffect, useState } from 'react'; + +import { getAllUrls, createUrl, deleteUrl } from '../../APIFunctions/Cleezy.js'; +import { trashcanSymbol } from '../Overview/SVG.js'; +import ConfirmationModal from '../../Components/DecisionModal/ConfirmationModal.js'; + +export default function URLShortenerPage(props) { + const [isCleezyDisabled, setIsCleezyDisabled] = useState(false); + const [url, setUrl] = useState(''); + const [invalidUrl, setInvalidUrl] = useState(); + const [showUrlInput, setShowUrlInput] = useState(false); + const [useGeneratedAlias, setUseGeneratedAlias] = useState(false); + const [alias, setAlias] = useState(''); + const [allUrls, setAllUrls] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(); + const [successMessage, setSuccessMessage] = useState(); + const [aliasTaken, setAliasTaken] = useState(); + const [page, setPage] = useState(0); + const [total, setTotal] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(10); + const [searchQuery, setSearchQuery] = useState(null); + const [invalidSearch, setInvalidSearch] = useState(false); + const [errorAlertMessage, setErrorAlertMessage] = useState(''); + const [urlToDelete, setUrlToDelete] = useState({}); + const [toggleDelete, setToggleDelete] = useState(false); + const [currentSortColumn, setCurrentSortColumn] = useState(null); + const [currentSortOrder, setCurrentSortOrder] = useState(null); + + const INPUT_CLASS = + 'indent-2 block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 text-gray'; + const LABEL_CLASS = 'block text-sm font-medium leading-6 text-gray-300'; + + /** + * Cleezy page is disabled by default since you have to run the Cleezy server + * separately. To enable, go to config.json and set ENABLED under Cleezy to true + */ + async function getCleezyUrls( + page, + searchQuery, + currentSortColumn, + currentSortOrder + ) { + setLoading(true); + const sortColumn = currentSortColumn ?? 'created_at'; + const sortOrder = currentSortOrder ?? 'DESC'; + const urlsFromDb = await getAllUrls({ + token: props.user.token, + page: page, + search: searchQuery, + sortColumn: sortColumn, + sortOrder: sortOrder, + }); + setIsCleezyDisabled(!!urlsFromDb.responseData.disabled); + if (urlsFromDb.error) { + setError(urlsFromDb.responseData); + } else { + setAllUrls(urlsFromDb.responseData.data); + setTotal(urlsFromDb.responseData.total - 1); + setRowsPerPage(urlsFromDb.responseData.rowsPerPage); + } + setLoading(false); + } + + async function handleCreateUrl() { + const response = await createUrl( + url.trim(), + alias.trim(), + props.user.token + ); + if (!response.error) { + setAllUrls([...allUrls, response.responseData]); + setAliasTaken(false); + setUrl(''); + setAlias(''); + setShowUrlInput(false); + setTotal(total + 1); + setSuccessMessage( + `Sucessfully created shortened link ${response.responseData.link}` + ); + setTimeout(() => { + setSuccessMessage(null); + }, 5000); + return true; + } else { + setAliasTaken(true); + setErrorAlertMessage('That alias is taken!'); + return false; + } + } + + async function maybeSubmitUrl() { + const regex = + /^(http(s)?:\/\/)[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_.+~#?&//=]*)$/; + if (regex.test(url.trim())) { + setInvalidUrl(false); + handleCreateUrl(); + } else { + setInvalidUrl(true); + setErrorAlertMessage('URL must be valid'); + return false; + } + } + + async function maybeSubmitSearch() { + const regex = /^[a-zA-Z0-9]+$/; + if (searchQuery === '' || regex.test(searchQuery)) { + setInvalidSearch(false); + getCleezyUrls(page, searchQuery); + } else { + setInvalidSearch(true); + setErrorAlertMessage('Search query cannot contain special characters'); + return false; + } + if (loading && !allUrls.length) { + return
; + } + } + + async function handleDeleteUrl(alias) { + const response = await deleteUrl(alias, props.user.token); + if (!response.error) { + setAllUrls(allUrls.filter((url) => url.alias !== alias)); + setTotal(total - 1); + } + } + + function handleSortUrls(columnName) { + if (columnName === null) { + return; + } + if (currentSortColumn === columnName) { + if (currentSortOrder === 'ASC') { + setCurrentSortOrder('DESC'); + } else if (currentSortOrder === 'DESC') { + setCurrentSortOrder(null); + setCurrentSortColumn(null); + } + } else { + setCurrentSortColumn(columnName); + setCurrentSortOrder('ASC'); + } + } + + function handleArrowVisibility(sortOrder, columnName) { + if (currentSortOrder === sortOrder && currentSortColumn === columnName) + return ''; + return 'hidden'; + } + + useEffect(() => { + if (useGeneratedAlias) { + setAlias(''); + } + }, [useGeneratedAlias]); + + useEffect(() => { + if (!showUrlInput) { + setAlias(''); + setUrl(''); + } + }, [showUrlInput]); + + useEffect(() => { + setInvalidUrl(false); + }, [url]); + + useEffect(() => { + setInvalidSearch(false); + }, [searchQuery]); + + useEffect(() => { + setAliasTaken(false); + }, [alias]); + + useEffect(() => { + getCleezyUrls(page, searchQuery, currentSortColumn, currentSortOrder); + }, [page, currentSortColumn, currentSortOrder]); + + function maybeRenderErrorAlert() { + if (invalidUrl || aliasTaken || invalidSearch) { + return ( +
+ + + + {errorAlertMessage} +
+ ); + } + } + + function maybeRenderPagination() { + const amountOfUrlsOnPage = Math.min( + (page + 1) * rowsPerPage, + allUrls.length + ); + const pageOffset = page * rowsPerPage; + const endingElementNumber = amountOfUrlsOnPage + pageOffset; + const startingElementNumber = page * rowsPerPage + 1; + if (!allUrls.length) { + return <>; + } else { + return ( + + ); + } + } + + function renderUrlButtonOrForm() { + if (!showUrlInput) { + return ( + + ); + } + + return ( +
+
+
+

+ Create a new link +

+ +
+ {!useGeneratedAlias && ( +
+ +
+ setAlias(e.target.value)} + className='w-full text-sm input input-bordered sm:text-base' + /> +
+
+ )} + +
+
+ +
+
+ +
+ +
+ setUrl(e.target.value)} + className='w-full text-sm input input-bordered sm:text-base' + /> +
+
+
+
+
+ +
+ + +
+
+ ); + } + + function maybeRenderSearch() { + return ( + <> +
+ + Type a search, followed by the enter key + +
+ { + if (event.key === 'Enter') { + event.preventDefault(); + // instead of calling the backend directory, set + // the page we are on to zero if the current page + // we are on isn't the first page (value of 0). + // by doing this, the useEffect will call the backend + // for us with the correct page and query. + if (page) { + setPage(0); + } else { + maybeSubmitSearch(); + } + } + }} + onChange={(event) => { + setSearchQuery(event.target.value); + }} + /> + + ); + } + + if (isCleezyDisabled) { + return ( +
+

+ Cleezy page is disabled. +

+

To enable:

+
    +
  1. + Modify
    api/config/config.json
    to include + 'Cleezy': { 'ENABLED': true } +
  2. +
  3. + Clone Cleezy locally and follow the steps in{' '} + + the readme + {' '} + to run locally. +
  4. +
  5. Run Clark and log in with an Admin account.
  6. +
+
+ ); + } + + if (error) { + return ( +
+

+ Unable to load URL table: +

+

{String(error)}

+
+ ); + } + + return ( + // the below input layout is taken from + // https://tailwindui.com/components/application-ui/forms/form-layouts +
+ { + handleDeleteUrl(urlToDelete.alias); + setToggleDelete(false); + }, + handleCancel: () => { + setToggleDelete(false); + }, + open: toggleDelete, + }} + /> +
+
+ {maybeRenderErrorAlert()} + {successMessage && ( +
+
+ + + + {successMessage} +
+
+ )} +
+ {renderUrlButtonOrForm()} +
+
+
{maybeRenderSearch()}
+
+ + + + {[ + { + title: 'URL', + className: 'text-base text-white/70', + columnName: 'alias', + }, + { + title: 'Created At', + className: + 'text-base text-white/70 hidden text-center sm:table-cell', + columnName: 'created_at', + }, + { + title: 'Times Used', + className: 'text-base text-white/70 text-center', + columnName: 'used', + }, + { + title: 'Delete', + className: 'text-base text-white/70 text-center', + }, + ].map(({ title, className, columnName = null }) => ( + + ))} + + + + + {allUrls.map((url, index) => { + return ( + + + + + + + ); + })} + +
+
+ + + + + + + +
+
+ + {url.alias} + +

+ {url.url.length > 60 + ? url.url.slice(0, 50) + '...' + : url.url} +

+
+
+ {new Date(url.created_at).toLocaleDateString( + 'en-US', + { + month: 'short', + day: 'numeric', + year: 'numeric', + } + )}{' '} +
+
+
+ {url.used} +
+
+
+ +
+
+ {allUrls.length === 0 && ( +
+

+ No results found! +

+
+ )} + {maybeRenderPagination()} +
+
+
+
+
+ ); +} diff --git a/src/Pages/URLShortener/URLShortener.js b/src/Pages/URLShortener/URLShortener.js index 0b1e73b10..98fd2e854 100644 --- a/src/Pages/URLShortener/URLShortener.js +++ b/src/Pages/URLShortener/URLShortener.js @@ -1,5 +1,6 @@ import React, { useEffect, useState } from 'react'; + import { getAllUrls, createUrl, deleteUrl } from '../../APIFunctions/Cleezy'; import { trashcanSymbol } from '../Overview/SVG'; import ConfirmationModal from '../../Components/DecisionModal/ConfirmationModal.js'; @@ -27,20 +28,14 @@ export default function URLShortenerPage(props) { const [currentSortColumn, setCurrentSortColumn] = useState(null); const [currentSortOrder, setCurrentSortOrder] = useState(null); - const INPUT_CLASS = - 'indent-2 block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 text-gray'; + const INPUT_CLASS = 'indent-2 block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 text-gray'; const LABEL_CLASS = 'block text-sm font-medium leading-6 text-gray-300'; /** * Cleezy page is disabled by default since you have to run the Cleezy server * separately. To enable, go to config.json and set ENABLED under Cleezy to true */ - async function getCleezyUrls( - page, - searchQuery, - currentSortColumn, - currentSortOrder - ) { + async function getCleezyUrls(page, searchQuery, currentSortColumn, currentSortOrder) { setLoading(true); const sortColumn = currentSortColumn ?? 'created_at'; const sortOrder = currentSortOrder ?? 'DESC'; @@ -49,7 +44,7 @@ export default function URLShortenerPage(props) { page: page, search: searchQuery, sortColumn: sortColumn, - sortOrder: sortOrder, + sortOrder: sortOrder }); setIsCleezyDisabled(!!urlsFromDb.responseData.disabled); if (urlsFromDb.error) { @@ -75,9 +70,7 @@ export default function URLShortenerPage(props) { setAlias(''); setShowUrlInput(false); setTotal(total + 1); - setSuccessMessage( - `Sucessfully created shortened link ${response.responseData.link}` - ); + setSuccessMessage(`Sucessfully created shortened link ${response.responseData.link}`); setTimeout(() => { setSuccessMessage(null); }, 5000); @@ -118,9 +111,10 @@ export default function URLShortenerPage(props) { } async function handleDeleteUrl(alias) { + const response = await deleteUrl(alias, props.user.token); if (!response.error) { - setAllUrls(allUrls.filter((url) => url.alias !== alias)); + setAllUrls(allUrls.filter(url => url.alias !== alias)); setTotal(total - 1); } } @@ -129,7 +123,7 @@ export default function URLShortenerPage(props) { if (columnName === null) { return; } - if (currentSortColumn === columnName) { + if(currentSortColumn === columnName) { if (currentSortOrder === 'ASC') { setCurrentSortOrder('DESC'); } else if (currentSortOrder === 'DESC') { @@ -148,6 +142,7 @@ export default function URLShortenerPage(props) { return 'hidden'; } + useEffect(() => { if (useGeneratedAlias) { setAlias(''); @@ -180,34 +175,19 @@ export default function URLShortenerPage(props) { function maybeRenderErrorAlert() { if (invalidUrl || aliasTaken || invalidSearch) { return ( -
- - - - {errorAlertMessage} +
+ + { errorAlertMessage }
); } } function maybeRenderPagination() { - const amountOfUrlsOnPage = Math.min( - (page + 1) * rowsPerPage, - allUrls.length - ); + const amountOfUrlsOnPage = Math.min((page + 1) * rowsPerPage, allUrls.length); const pageOffset = page * rowsPerPage; const endingElementNumber = amountOfUrlsOnPage + pageOffset; - const startingElementNumber = page * rowsPerPage + 1; + const startingElementNumber = (page * rowsPerPage) + 1; if (!allUrls.length) { return <>; } else { @@ -215,22 +195,14 @@ export default function URLShortenerPage(props) {
); } function maybeRenderSearch() { return ( - <> -
- - Type a search, followed by the enter key - -
- { - if (event.key === 'Enter') { - event.preventDefault(); - // instead of calling the backend directory, set - // the page we are on to zero if the current page - // we are on isn't the first page (value of 0). - // by doing this, the useEffect will call the backend - // for us with the correct page and query. - if (page) { - setPage(0); - } else { - maybeSubmitSearch(); - } + <>
+ Type a search, followed by the enter key +
{ + if (event.key === 'Enter') { + event.preventDefault(); + // instead of calling the backend directory, set + // the page we are on to zero if the current page + // we are on isn't the first page (value of 0). + // by doing this, the useEffect will call the backend + // for us with the correct page and query. + if (page) { + setPage(0); + } else { + maybeSubmitSearch(); } - }} - onChange={(event) => { - setSearchQuery(event.target.value); - }} - /> - + } + } } + onChange={event => { + setSearchQuery(event.target.value); + } } /> ); } @@ -392,21 +349,22 @@ export default function URLShortenerPage(props) {

To enable:

    -
  1. - Modify
    api/config/config.json
    to include - 'Cleezy': { 'ENABLED': true } +
  2. Modify
    api/config/config.json
    to include + + 'Cleezy': { + 'ENABLED': true + } +
  3. - Clone Cleezy locally and follow the steps in{' '} - + Clone Cleezy locally and follow the steps in + {' '} the readme - {' '} - to run locally. + to run locally. +
  4. +
  5. + Run Clark and log in with an Admin account.
  6. -
  7. Run Clark and log in with an Admin account.
); @@ -424,117 +382,64 @@ export default function URLShortenerPage(props) { } return ( - // the below input layout is taken from - // https://tailwindui.com/components/application-ui/forms/form-layouts + // the below input layout is taken from + // https://tailwindui.com/components/application-ui/forms/form-layouts
- { - handleDeleteUrl(urlToDelete.alias); - setToggleDelete(false); - }, - handleCancel: () => { - setToggleDelete(false); - }, - open: toggleDelete, - }} - /> + confirmText: `Yes, delete ${urlToDelete.alias}`, + confirmClassAddons: 'bg-red-600 hover:bg-red-500', + handleConfirmation: () => { + handleDeleteUrl(urlToDelete.alias); + setToggleDelete(false); + }, + handleCancel: () => { + setToggleDelete(false); + }, + open: toggleDelete + } + } />
{maybeRenderErrorAlert()} - {successMessage && ( + {successMessage &&
-
- - - +
+ {successMessage}
- )} + }
{renderUrlButtonOrForm()}
-
{maybeRenderSearch()}
+
+ {maybeRenderSearch()} +
{[ - { - title: 'URL', - className: 'text-base text-white/70', - columnName: 'alias', - }, - { - title: 'Created At', - className: - 'text-base text-white/70 hidden text-center sm:table-cell', - columnName: 'created_at', - }, - { - title: 'Times Used', - className: 'text-base text-white/70 text-center', - columnName: 'used', - }, - { - title: 'Delete', - className: 'text-base text-white/70 text-center', - }, + { title: 'URL', className: 'text-base text-white/70', columnName: 'alias' }, + { title: 'Created At', className: 'text-base text-white/70 hidden text-center sm:table-cell', columnName: 'created_at' }, + { title: 'Times Used', className: 'text-base text-white/70 text-center', columnName: 'used' }, + { title: 'Delete', className: 'text-base text-white/70 text-center' } ].map(({ title, className, columnName = null }) => ( - @@ -545,36 +450,16 @@ export default function URLShortenerPage(props) { {allUrls.map((url, index) => { return ( - +
-
- - - + +
+ + + - - + +
- + {url.alias} -

- {url.url.length > 60 - ? url.url.slice(0, 50) + '...' - : url.url} -

+

{url.url.length > 60 ? url.url.slice(0, 50) + '...' : url.url}

- {new Date(url.created_at).toLocaleDateString( - 'en-US', - { - month: 'short', - day: 'numeric', - year: 'numeric', - } - )}{' '} -
+ {new Date(url.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}
@@ -584,12 +469,10 @@ export default function URLShortenerPage(props) {
@@ -601,9 +484,7 @@ export default function URLShortenerPage(props) {
{allUrls.length === 0 && (
-

- No results found! -

+

No results found!

)} {maybeRenderPagination()} From 66cbd5fd82b226be954908e3f995d351c8b5a4f8 Mon Sep 17 00:00:00 2001 From: kingsand Date: Mon, 23 Jun 2025 11:08:43 -0700 Subject: [PATCH 3/3] Remove unused file, and darkened text on light mode --- src/Pages/URLShortener/2DPrinting.js | 616 ------------------------- src/Pages/URLShortener/URLShortener.js | 26 +- 2 files changed, 13 insertions(+), 629 deletions(-) delete mode 100644 src/Pages/URLShortener/2DPrinting.js diff --git a/src/Pages/URLShortener/2DPrinting.js b/src/Pages/URLShortener/2DPrinting.js deleted file mode 100644 index 3c95b0081..000000000 --- a/src/Pages/URLShortener/2DPrinting.js +++ /dev/null @@ -1,616 +0,0 @@ -import React, { useEffect, useState } from 'react'; - -import { getAllUrls, createUrl, deleteUrl } from '../../APIFunctions/Cleezy.js'; -import { trashcanSymbol } from '../Overview/SVG.js'; -import ConfirmationModal from '../../Components/DecisionModal/ConfirmationModal.js'; - -export default function URLShortenerPage(props) { - const [isCleezyDisabled, setIsCleezyDisabled] = useState(false); - const [url, setUrl] = useState(''); - const [invalidUrl, setInvalidUrl] = useState(); - const [showUrlInput, setShowUrlInput] = useState(false); - const [useGeneratedAlias, setUseGeneratedAlias] = useState(false); - const [alias, setAlias] = useState(''); - const [allUrls, setAllUrls] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(); - const [successMessage, setSuccessMessage] = useState(); - const [aliasTaken, setAliasTaken] = useState(); - const [page, setPage] = useState(0); - const [total, setTotal] = useState(0); - const [rowsPerPage, setRowsPerPage] = useState(10); - const [searchQuery, setSearchQuery] = useState(null); - const [invalidSearch, setInvalidSearch] = useState(false); - const [errorAlertMessage, setErrorAlertMessage] = useState(''); - const [urlToDelete, setUrlToDelete] = useState({}); - const [toggleDelete, setToggleDelete] = useState(false); - const [currentSortColumn, setCurrentSortColumn] = useState(null); - const [currentSortOrder, setCurrentSortOrder] = useState(null); - - const INPUT_CLASS = - 'indent-2 block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 text-gray'; - const LABEL_CLASS = 'block text-sm font-medium leading-6 text-gray-300'; - - /** - * Cleezy page is disabled by default since you have to run the Cleezy server - * separately. To enable, go to config.json and set ENABLED under Cleezy to true - */ - async function getCleezyUrls( - page, - searchQuery, - currentSortColumn, - currentSortOrder - ) { - setLoading(true); - const sortColumn = currentSortColumn ?? 'created_at'; - const sortOrder = currentSortOrder ?? 'DESC'; - const urlsFromDb = await getAllUrls({ - token: props.user.token, - page: page, - search: searchQuery, - sortColumn: sortColumn, - sortOrder: sortOrder, - }); - setIsCleezyDisabled(!!urlsFromDb.responseData.disabled); - if (urlsFromDb.error) { - setError(urlsFromDb.responseData); - } else { - setAllUrls(urlsFromDb.responseData.data); - setTotal(urlsFromDb.responseData.total - 1); - setRowsPerPage(urlsFromDb.responseData.rowsPerPage); - } - setLoading(false); - } - - async function handleCreateUrl() { - const response = await createUrl( - url.trim(), - alias.trim(), - props.user.token - ); - if (!response.error) { - setAllUrls([...allUrls, response.responseData]); - setAliasTaken(false); - setUrl(''); - setAlias(''); - setShowUrlInput(false); - setTotal(total + 1); - setSuccessMessage( - `Sucessfully created shortened link ${response.responseData.link}` - ); - setTimeout(() => { - setSuccessMessage(null); - }, 5000); - return true; - } else { - setAliasTaken(true); - setErrorAlertMessage('That alias is taken!'); - return false; - } - } - - async function maybeSubmitUrl() { - const regex = - /^(http(s)?:\/\/)[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_.+~#?&//=]*)$/; - if (regex.test(url.trim())) { - setInvalidUrl(false); - handleCreateUrl(); - } else { - setInvalidUrl(true); - setErrorAlertMessage('URL must be valid'); - return false; - } - } - - async function maybeSubmitSearch() { - const regex = /^[a-zA-Z0-9]+$/; - if (searchQuery === '' || regex.test(searchQuery)) { - setInvalidSearch(false); - getCleezyUrls(page, searchQuery); - } else { - setInvalidSearch(true); - setErrorAlertMessage('Search query cannot contain special characters'); - return false; - } - if (loading && !allUrls.length) { - return
; - } - } - - async function handleDeleteUrl(alias) { - const response = await deleteUrl(alias, props.user.token); - if (!response.error) { - setAllUrls(allUrls.filter((url) => url.alias !== alias)); - setTotal(total - 1); - } - } - - function handleSortUrls(columnName) { - if (columnName === null) { - return; - } - if (currentSortColumn === columnName) { - if (currentSortOrder === 'ASC') { - setCurrentSortOrder('DESC'); - } else if (currentSortOrder === 'DESC') { - setCurrentSortOrder(null); - setCurrentSortColumn(null); - } - } else { - setCurrentSortColumn(columnName); - setCurrentSortOrder('ASC'); - } - } - - function handleArrowVisibility(sortOrder, columnName) { - if (currentSortOrder === sortOrder && currentSortColumn === columnName) - return ''; - return 'hidden'; - } - - useEffect(() => { - if (useGeneratedAlias) { - setAlias(''); - } - }, [useGeneratedAlias]); - - useEffect(() => { - if (!showUrlInput) { - setAlias(''); - setUrl(''); - } - }, [showUrlInput]); - - useEffect(() => { - setInvalidUrl(false); - }, [url]); - - useEffect(() => { - setInvalidSearch(false); - }, [searchQuery]); - - useEffect(() => { - setAliasTaken(false); - }, [alias]); - - useEffect(() => { - getCleezyUrls(page, searchQuery, currentSortColumn, currentSortOrder); - }, [page, currentSortColumn, currentSortOrder]); - - function maybeRenderErrorAlert() { - if (invalidUrl || aliasTaken || invalidSearch) { - return ( -
- - - - {errorAlertMessage} -
- ); - } - } - - function maybeRenderPagination() { - const amountOfUrlsOnPage = Math.min( - (page + 1) * rowsPerPage, - allUrls.length - ); - const pageOffset = page * rowsPerPage; - const endingElementNumber = amountOfUrlsOnPage + pageOffset; - const startingElementNumber = page * rowsPerPage + 1; - if (!allUrls.length) { - return <>; - } else { - return ( - - ); - } - } - - function renderUrlButtonOrForm() { - if (!showUrlInput) { - return ( - - ); - } - - return ( -
-
-
-

- Create a new link -

- -
- {!useGeneratedAlias && ( -
- -
- setAlias(e.target.value)} - className='w-full text-sm input input-bordered sm:text-base' - /> -
-
- )} - -
-
- -
-
- -
- -
- setUrl(e.target.value)} - className='w-full text-sm input input-bordered sm:text-base' - /> -
-
-
-
-
- -
- - -
-
- ); - } - - function maybeRenderSearch() { - return ( - <> -
- - Type a search, followed by the enter key - -
- { - if (event.key === 'Enter') { - event.preventDefault(); - // instead of calling the backend directory, set - // the page we are on to zero if the current page - // we are on isn't the first page (value of 0). - // by doing this, the useEffect will call the backend - // for us with the correct page and query. - if (page) { - setPage(0); - } else { - maybeSubmitSearch(); - } - } - }} - onChange={(event) => { - setSearchQuery(event.target.value); - }} - /> - - ); - } - - if (isCleezyDisabled) { - return ( -
-

- Cleezy page is disabled. -

-

To enable:

-
    -
  1. - Modify
    api/config/config.json
    to include - 'Cleezy': { 'ENABLED': true } -
  2. -
  3. - Clone Cleezy locally and follow the steps in{' '} - - the readme - {' '} - to run locally. -
  4. -
  5. Run Clark and log in with an Admin account.
  6. -
-
- ); - } - - if (error) { - return ( -
-

- Unable to load URL table: -

-

{String(error)}

-
- ); - } - - return ( - // the below input layout is taken from - // https://tailwindui.com/components/application-ui/forms/form-layouts -
- { - handleDeleteUrl(urlToDelete.alias); - setToggleDelete(false); - }, - handleCancel: () => { - setToggleDelete(false); - }, - open: toggleDelete, - }} - /> -
-
- {maybeRenderErrorAlert()} - {successMessage && ( -
-
- - - - {successMessage} -
-
- )} -
- {renderUrlButtonOrForm()} -
-
-
{maybeRenderSearch()}
-
- - - - {[ - { - title: 'URL', - className: 'text-base text-white/70', - columnName: 'alias', - }, - { - title: 'Created At', - className: - 'text-base text-white/70 hidden text-center sm:table-cell', - columnName: 'created_at', - }, - { - title: 'Times Used', - className: 'text-base text-white/70 text-center', - columnName: 'used', - }, - { - title: 'Delete', - className: 'text-base text-white/70 text-center', - }, - ].map(({ title, className, columnName = null }) => ( - - ))} - - - - - {allUrls.map((url, index) => { - return ( - - - - - - - ); - })} - -
-
- - - - - - - -
-
- - {url.alias} - -

- {url.url.length > 60 - ? url.url.slice(0, 50) + '...' - : url.url} -

-
-
- {new Date(url.created_at).toLocaleDateString( - 'en-US', - { - month: 'short', - day: 'numeric', - year: 'numeric', - } - )}{' '} -
-
-
- {url.used} -
-
-
- -
-
- {allUrls.length === 0 && ( -
-

- No results found! -

-
- )} - {maybeRenderPagination()} -
-
-
-
-
- ); -} diff --git a/src/Pages/URLShortener/URLShortener.js b/src/Pages/URLShortener/URLShortener.js index 98fd2e854..d134a62ac 100644 --- a/src/Pages/URLShortener/URLShortener.js +++ b/src/Pages/URLShortener/URLShortener.js @@ -28,8 +28,8 @@ export default function URLShortenerPage(props) { const [currentSortColumn, setCurrentSortColumn] = useState(null); const [currentSortOrder, setCurrentSortOrder] = useState(null); - const INPUT_CLASS = 'indent-2 block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 text-gray'; - const LABEL_CLASS = 'block text-sm font-medium leading-6 text-gray-300'; + const INPUT_CLASS = 'indent-2 block w-full rounded-md border-0 py-1.5 text-slate-800 dark:text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-slate-700 dark:placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 text-gray'; + const LABEL_CLASS = 'block text-sm font-medium leading-6 text-slate-800 dark:text-gray-300'; /** * Cleezy page is disabled by default since you have to run the Cleezy server @@ -230,7 +230,7 @@ export default function URLShortenerPage(props) { if (!showUrlInput) { return (
} @@ -420,14 +420,14 @@ export default function URLShortenerPage(props) { {maybeRenderSearch()}
- +
{[ - { title: 'URL', className: 'text-base text-white/70', columnName: 'alias' }, - { title: 'Created At', className: 'text-base text-white/70 hidden text-center sm:table-cell', columnName: 'created_at' }, - { title: 'Times Used', className: 'text-base text-white/70 text-center', columnName: 'used' }, - { title: 'Delete', className: 'text-base text-white/70 text-center' } + { title: 'URL', className: 'text-base text-slate-800 dark:text-white/70', columnName: 'alias' }, + { title: 'Created At', className: 'text-base text-slate-800 dark:text-white/70 hidden text-center sm:table-cell', columnName: 'created_at' }, + { title: 'Times Used', className: 'text-base text-slate-800 dark:text-white/70 text-center', columnName: 'used' }, + { title: 'Delete', className: 'text-base text-slate-800 dark:text-white/70 text-center' } ].map(({ title, className, columnName = null }) => (
{allUrls.length === 0 && (
-

No results found!

+

No results found!

)} {maybeRenderPagination()}